Is a more generic solution possible? - c#

I think what I want to do is not possible.
However, I would like to make sure that it is indeed not possible.
I use Audit.Net as a framework to add auditing to a system that has already been completed.
There are many different objects that may be sent for auditing. I have to get hold of the properties of those objects and send them to the database. In some cases, I would want the old values and the new values, and therefore I use the Target property of AuditEvent, otherwise if I just need the new values I use the CustomField property.
Is there any way to make the following more generic, so that I don't have to repeat these lines for each type of object like SimpleResult, LeaveRequest, Incident etc?
There is unfortunately no commonality between the objects being audited.
SimpleResult objOld = JsonConvert.DeserializeObject<SimpleResult>(auditEvent.Target.SerializedOld.ToString());
SimpleResult objNew = JsonConvert.DeserializeObject<SimpleResult>(auditEvent.Target.SerializedNew.ToString());
if (auditEvent.Target.Type.ToString() == "SimpleResult")
{
InsertTargetObjectFields<SimpleResult>(objOld, objNew, auditControlTableID, auditEvent);
}
Here is where I get hold of the properties and send them off to the database:
public void InsertTargetObjectFields<T>(T objOld, T objNew, int? auditControlTableID, AuditEvent auditEvent)
{
using (ESSDataContext ess_context = new ESSDataContext())
{
try
{
foreach (var property in objOld.GetType().GetProperties().Where(property => !property.GetGetMethod().GetParameters().Any()))
{
//Check for null values and get hold of oldValue and newValue
var sqlResult = ess_context.InsertAuditTable(resourceTag, dbObjectName, username, property.Name, oldValue,
newValue, auditEvent.StartDate, auditControlTableID.ToString(),
auditEvent.Environment.CallingMethodName);
}
}
}
}
I've tried using dynamic, but then I don't get the properties correctly.

Related

Map/merge two objects using Automapper but ignoring default values

I've already looked into a lot of the posts on SO of people having similar problems, but nothing has worked so far.
I have an two instances of an object. One is being used in another class, while the other one is being periodically (about every 5 seconds or so) updated with new information via websocket.
The first response from the websocket upon subscription is the object with all of it's properties assigned a value. The problem is that the subsequent data coming in the from websocket only brings in data that has changed, so when I go to deserialize, it sets those properties that weren't assigned a value, their default value.
I'd rather not have to do a ".ForMember" on each of the properties since this object has 52 of them.
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<StreamingQuoteResponse.Content, StreamingQuoteResponse.Content>().ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
});
var mapper = new Mapper(configuration);
var newQuote = mapper.Map<StreamingQuoteResponse.Content>(quote);
As far as I can tell, in the above code, only the strings would be null by default. Floats would be 0; doubles, 0.0d, etc.
The object has all different types of properties (bool, double, float, char, string). Is there a way to ignore mapping of a single property if the source property is the default value? I was thinking there was a way to get the property type using AutoMapper's (Pre)Condition, and then if it's e.g. typeof(float), then we can ignore mapping if the source value is 0? I'm not sure if you can do that though.
Or maybe a subroutine/method that would resolve all of this using Reflections?
This might help someone in the future, but I ended up ditching the AutoMapper, and going with JSON.NET. I didn't know they had this 'JsonMergeSettings'. I also had to add an attribute to all the properties of the class I'm (de)serializing. For example:
public class Content
{
[JsonProperty("key", DefaultValueHandling = DefaultValueHandling.Ignore)]
public string Key { get; set; }
}
Then in the event handler that was receiving all the websocket data, I did the following:
private bool firstFlag = true;
private JObject jObject;
private void HandleStreamingWebsocketData(object sender, MessageEventArgs e)
{
if (firstFlag)
{
jObject = JObject.Parse(e.Data);
firstFlag = false;
}
jObject.Merge(JObject.Parse(e.Data), new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Merge
});
}
Hopefully this helps someone trying to do the same thing. And I'm sure it can be done with AutoMapper, but this was much easier I think, and most likely a bit faster, since you're merging the JSON before it's being deserialized into a C# object, as opposed to deserializing the updated data and comparing it to the deserialized previous data, and the mapping them that way.

Ignore null values in context update

I have a large model which has been partially updated via deserialization. Since it has only been partially updated I would like to ignore any null values when I pass this to my entity framework update. Ultimately the EntityState.Modified is set but the issue I am having is that all fields are updated. This means anything that was null is now blanked in the database.
Is it possible to change this default behavior through a setting or override a method to check for null? It seems that since the context is expecting the full model I cannot simply set only a few values.
I've verified this by mapping only what I need to modify and the same behavior occurs.
You could implement something like this.
In this case I'm using a generic repository with reflection, to iterate through the properties and exclude null values in the update method.
public virtual TEntity Update(TEntity entity)
{
dbSet.Attach(entity);
dbContext.Entry(entity).State = EntityState.Modified;
var entry = dbContext.Entry(entity);
Type type = typeof(TEntity);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.GetValue(entity, null) == null)
{
entry.Property(property.Name).IsModified = false;
}
}
dbContext.SaveChanges();
return entity;
}
public IHttpActionResult PutProduct(int id, Product product)
{
NorthwindEntities db = new NorthwindEntities();
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Attach(product);
// Only the fields you want to update, will change.
db.Entry(product).Property(p => p.ProductName).IsModified = true;
db.Entry(product).Property(p => p.UnitPrice).IsModified = true;
db.Entry(product).Property(p => p.UnitsInStock).IsModified = true;
// only if if the value is not null, the field will change.
db.Entry(product).Property(p => p.UnitsOnOrder).IsModified =
product.UnitsOnOrder != null;
db.SaveChanges();
return Ok(product);
}
This is a somewhat tedious problem I've had to solve.
For lack of a more straightforward solution, eventually I decided I'd rather not want to try solving it as much downstream as when EF's SaveChanges-and-the-likes' get called (i.e., no need to "hook into" EF so late), but instead as much higher upstream / earlier on as possible --
that is, to do so right after I obtain a satisfying deserialization, which is meaningful to mutate the model, on a per-entity instance basis (in my use case, none of the updatable properties would represent relationships, but only independent attributes, in E/R parlance -- YMMV)
So, I opted for a "Populate" helper, along the lines of:
static void Populate(object from, object to)
{
var sourceType = from.GetType();
foreach (PropertyInfo target in to.GetType().GetProperties())
{
// Is the property at the target object writable and *not* marked
// as `[NotMapped]'?
var isUpdatable =
target.CanWrite &&
(target.GetCustomAttribute<NotMappedAttribute>(true) == null);
if (isUpdatable)
{
// If so, just find the corresp. property with the same name at the source object
// (caller is assumed responsible to guarantee that there is one, and of the same type, here)
var source = sourceType.GetProperty(target.Name);
var #default = sourceType.IsValueType ? Activator.CreateInstance(sourceType) : null;
var equality = (IEqualityComparer)typeof(EqualityComparer<>).MakeGenericType(sourceType).GetProperty("Default", BindingFlags.Public | BindingFlags.Static).GetValue(null);
var value = source.GetValue(from);
// Test for <property value> != default(<property type>)
// (as we don't want to lose information on the target because of a "null" (or "default(...)") coming from the source)
if (!equality.Equals(value, #default))
{
target.SetValue(to, value, null);
}
}
}
}
where "from" is the fresh entity instance that just got partially populated by whatever deserialization code, and where "to" is the actual target entity that lives in the DbContext (be it an EF proxy or not);
and where NotMappedAttribute is the EF's usual.
You'd typically have Populate to be called some time after the deserialization (&/or DTO-mapping) onto your "from" instance is done, but anyway before SaveChanges() gets call on your DbContext for all the "to" entities -- obviously, we assume there is a feasible 1-to-1 mapping "from" ... "to", that the caller of Populate knows about / could figure out.
Note I still don't know if there is a more elegant (more straightforward) way to do that, without recourse to reflection -- so, there, FWIW.
Remarks
1) above code can (or should) be made more defensive in various ways, depending on the caller assumptions;
2) one may want to cache those IEqualityComparer's (&/or the PropertyInfo's) for whatever (good) reason may arise -- in my case, I didn't have to;
3) finally, my understanding is that third-party librairies such as AutoMapper are also specially designed for that sort of task, if you can afford the additional dependency
'HTH,

Audit Entity Framework deleted entries preserving previous value

I've been struggling for a while with a problem that consists on auditing generically database entities when they're saved. I have a project that uses EF 6 and it was required to me to create a "non-invasive" method to audit entities when they're added, modified or deleted. I have to store a JSON of the inserted entity, modified entity or deleted entity without interfering with the normal flow. The project has a Database First implementation.
My solution was simple, add a partial class of any entity that the rest of the programmers want to audit implementing IAudit which is basically an empty interface to get all changes from entities that implement it.
public interface IAudit {}
I have a Currencies entity that just implement it without any other code (I could do something else in the future but I don't need it)
public partial class Currencies : IAudit
I override the SaveChanges method to look for entities to audit
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
// This linq looks for new entities that were marked for audit
CreateAuditLog(System.Data.Entity.EntityState.Added);
CreateAuditLog(System.Data.Entity.EntityState.Modified);
CreateAuditLog(System.Data.Entity.EntityState.Deleted);
return base.SaveChanges();
}
The solution calls 3 times the CreateAuditLog because in the near future I need to implement a configuration to audit whatever the user decides, might be from a database configuration that is activated/deactivated by users.
Everything worked perfectly, I was able to get saved entities in the specified state:
private void CreateAuditLog(System.Data.Entity.EntityState state)
{
var auditedEntities = ChangeTracker.Entries<IAudit>()
.Where(p => p.State == state)
.Select(p => p.Entity);
... some code that do something else
foreach (var auditedEntity in auditedEntities)
{
... some information I required to add
strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());
... some code to save audit information
}
}
The problem is I lose every value in the Deleted state, I only get the ID, there's no information in the properties except the ID and there is no any possibility of extract it in any way. I looked for every single solution in StackOverflow and other websites and there is nothing to recover the original information.
How can I get the previous deleted values to store them in the same way I'm storing Added and Modified entities?
It took me a couple of days to figure it out. Might be the solution is a bit complex but I tried several less complex options with not a good result.
First, as I'm just auditing Delete in a different way I separated Deleted state from Added and Modified that work well with no change. Deleted state is a particular case and I treat it like that.
First, I needed to obtain the original values from the database. In the Deleted state they're gone, there's not any possibility of recovering them from the entity. It's possible to obtain them with the following code:
var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();
The result is just a collection of DB property values (DbPropertyValues). If I can get the original values I set the original values from the deleted entity:
dbEntityEntry.OriginalValues.SetValues(databaseValues);
This line just fills the entity original values, it doesn't modify the current value at all. It's useful to do it that way because it takes some code to check every property and set it ourselves, it's an interesting shortcut.
Now, the problem is I don't have the entity to serialize, so I need a new one which in my case I create by reflection because I don't know the type (I receive entities that implement IAudit)
Type type = auditedEntity.GetType();
var auditDeletedEntity = Activator.CreateInstance(type);
This is the entity I will serialize to store the audit later.
Now, the complex part, I need to get the entity properties and fill them by reflection from the original values set in the entity:
foreach (var propertyInfo in type.GetProperties())
{
if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
{
var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
}
}
I had to check generic and array types to avoid following EF relations that are not going to work with this method and I also don't need (I need the object not the whole tree)
After that I simply need to serialize the audited deleted entity:
strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());
The code looks like this:
string strJSON = string.Empty;
if (state == System.Data.Entity.EntityState.Deleted)
{
var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();
// Get original values from the database (the only option, in the delete method they're lost)
DbEntityEntry dbEntityEntry = this.Entry(auditedEntity);
if (databaseValues != null)
{
dbEntityEntry.OriginalValues.SetValues(databaseValues);
var originalValues = this.Entry(auditedEntity).OriginalValues;
Type type = auditedEntity.GetType();
var auditDeletedEntity = Activator.CreateInstance(type);
// Get properties by reflection
foreach (var propertyInfo in type.GetProperties())
{
if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
{
var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
}
}
strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());
}
}
else
{
strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());
}
Might be there's a better way but I seriously spent a good amount of time looking for options and I couldn't find anything better.
Any suggestion or optimization is appreciated.

Object does not match target type PropertyInfo SetValue - one class to another

So I have 2 classes, both have identical Property names. One class contains different variables: int, strings, bool and DateTime The second class contains only 1 int and the rest are all strings.
Now I want to loop through all the properties, get the value from class1, encrypt that data and save it as a string in obj2, then return it to the main form (to save it in a database later).
public PersoonEncrypted EncryptPersonClass(Class1 object1)
{
PersoonEncrypted persEncrypt = new PersoonEncrypted(); //second class obj
Type type = object1.GetType();
PropertyInfo[] properties = type.GetProperties();
Type type2 = persEncrypt.GetType();
PropertyInfo[] properties2 = type.GetProperties();
foreach (var bothProperties in properties.Zip(properties2, (obj1, obj2) => new { Obj1 = obj1, Obj2 = obj2 }))
{
string value = "";
value = bothProperties.Obj1.GetValue(object1) as string;
if (!string.IsNullOrWhiteSpace(value))
{
string encryptValue = Encrypt(value);
if ((bothProperties.Obj2 != null) && (bothProperties.Obj2.PropertyType == typeof(string)))
{ //!= null check has no effect at all
bothProperties.Obj2.SetValue(persEncrypt, encryptValue, null); //errorLine
}
}
}
return persEncrypt;
}
That is what I came up with until now.
I have, of course, searched for other solutions like this one. This, after applying some own changes, didn't return any errors, but it didn't save any encrypted strings into the class persEncrypt. What I concluded was, from that test, is that it was testing if the value in the second class(persEncrypt in my example) from the particular property was null, while it shouldn't do that, it should make a new instance of that variable and save it in the object class, but removing that check gave me the same error.
you're just .Zip-ing the two lists of PropertyInfo objects, which simply iterates through both lists and doesn't check or sort for any sort of matching. This could result in erroneous behavior depending on the order in which properties appear - consider using a .Join instead to match property names.
This code doesn't check for an indexer on the property before attempting to assign to it without one - any indexed property which is of type string will make it to this point and then throw an exception when you try to set it.
Because this code is calling into Properties, there's the possibility an exception is being thrown by the code of the Property itself. This is where a StackTrace from your exception could reveal much more about what's happening.
Your code also checks for a property of type string directly - when using reflection you should use IsAssignableFrom instead in order to allow for inherited types, though that is unlikely the issue in this one case.

Passing properties as parameters to be Got and Set

Well, I need to repeat same code for many properties.
I've seen examples taking Action delegates, but they don't fit quite well here.
I want something like this: (see explanation below)
Dictionary<Property, object> PropertyCorrectValues;
public bool CheckValue(Property P) { return P.Value == PropertyCorrectValues[P]; }
public void DoCorrection(Property P) { P.Value = PropertyCorrectValues[P]; }
.
I want to have a dictionary containing many properties and their respective "correct" values. (I know it's not well declared, but that's the idea). Properties are not necessarely inside my class, some of them are in objects of different assemblies.
A method bool CheckValue(Property). This method must access the actual value of the property and compare to the correct value.
And a method a void DoCorrection(Property). This one sets the property value to the correct value.
Remember I have many of those properties, I wouldn't like to call the methods by hand for each property. I'd rather iterate through the dicionary in a foreach statement.
So, the main question is in the title.
I've tried the by ref, but properties don't accept that.
Am I obligated to use reflection??? Or is there another option (if I need, reflection answer will be accepted as well).
Is there anyway I can make a dictionary with pointers in C#? Or some kind of assignment that changes the value of variable's target instead of changing the target to another value?
Thanks for the help.
You can do this using reflection. Get a list of the properties on the object of interest with typeof(Foo).GetProperties(). Your PropertyCorrectValues property can have type IDictionary<PropertyInfo, object>. Then use the GetValue and SetValue methods on PropertyInfo to perform the desired operations:
public bool CheckProperty(object myObjectToBeChecked, PropertyInfo p)
{
return p.GetValue(myObjectToBeChecked, null).Equals(PropertyCorrectValues[p]);
}
public void DoCorrection(object myObjectToBeCorrected, PropertyInfo p)
{
p.SetValue(myObjectToBeCorrected, PropertyCorrectValues[p]);
}
In addition to Ben's code I'd like to contribute the following code fragment:
Dictionary<string,object> PropertyCorrectValues = new Dictionary<string,object>();
PropertyCorrectValues["UserName"] = "Pete"; // propertyName
PropertyCorrectValues["SomeClass.AccountData"] = "XYZ"; // className.propertyName
public void CheckAndCorrectProperties(object obj) {
if (obj == null) { return; }
// find all properties for given object that need to be checked
var checkableProps = from props
in obj.GetType().GetProperties()
from corr in PropertyCorrectValues
where (corr.Key.Contains(".") == false && props.Name == corr.Key) // propertyName
|| (corr.Key.Contains(".") == true && corr.Key.StartsWith(props.DeclaringType.Name + ".") && corr.Key.EndsWith("." + props.Name)) // className.propertyName
select new { Property = props, Key = corr.Key };
foreach (var pInfo in checkableProps) {
object propValue = pInfo.Property.GetValue(obj, null);
object expectedValue = PropertyCorrectValues[pInfo.Key];
// checking for equal value
if (((propValue == null) && (expectedValue != null)) || (propValue.Equals(expectedValue) == false)) {
// setting value
pInfo.Property.SetValue(obj, expectedValue, null);
}
}
}
When using this "automatic" value correction you might also consider:
You cannot create a PropertyInfo object just by knowing the property name and independently of the declaring class; that's why I chose string for the key.
When using the same property name in different classes then you might need to change the code that is doing the actual assignment because the type between the correct value and the property type might differ.
Using the same property name in different classes will always perform the same check (see point above), so you might need a syntax for property names to restrict it to a specific class (simple dot notation, doesn't work for namespaces or inner classes, but might be extended to do so)
If needed you can replace the "check" and "assign" part with separate method calls, but it might be done inside the code block as stated in my example code.

Categories

Resources