3

I use reflection to update objects which have had updates made to them and saved to mongodb

    private void updateSelf(MongoDoc newDoc)
    {
        Type type = this.GetType();
        foreach (var i in type.GetProperties())
        {
            if (i.GetCustomAttributes(false).Any(x => x is MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute)) continue;
            Object oldValue = i.GetValue(this, null);
            Object newValue = i.GetValue(newDoc, null);
            if (!Object.Equals(oldValue, newValue) && !((oldValue == null) && (newValue == null)))
            {
                i.SetValue(this, newValue, null);
            }
        }
    }

this is working for the most part but the i.SetValue(this, newValue, null); throws an exception when trying to update this property:

public uint Revision { get; private set; }

this is trying to update an object of type Product which is a derived type of MongoDoc which contains the property public uint Revision { get; private set; } which is causing the exception Property set Method not found I'm not sure what is causing this because it works on all my other properties, just this one throws and exception. Any help much appreciated

UPDATE:

I have tried the answer below:

i.SetValue(this, newValue, System.Reflection.BindingFlags.SetProperty | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic, null, null, null);

but unfortunately the exact same result, it still throws the exception on the Revision property.

UPDATE:

Exception:

System.ArgumentException was unhandled
  Message=Property set method not found.
  Source=mscorlib
  StackTrace:
       at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
       at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
       at Flo.Client.Docs.MongoDoc.updateSelf(MongoDoc newDoc) in F:\Flo\Flo.Client\Docs\MongoDoc.cs:line 162
       at Flo.Client.Docs.MongoDoc.UpdateToMongo(MongoDoc newDoc) in F:\Flo\Flo.Client\Docs\MongoDoc.cs:line 120
       at Flo.Client.Docs.Product.EditProduct(String Name, Nullable`1 State) in F:\Flo\Flo.Client\Docs\Product.cs:line 89
       at Flo.Client.Program.Main() in F:\Flo\Flo.Client\Program.cs:line 26
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 
L.B
  • 114,136
  • 19
  • 178
  • 224
Daniel Robinson
  • 13,806
  • 18
  • 64
  • 112
  • Do your properties all have `private set` accessors? – Tim S. Sep 14 '12 at 21:03
  • yes that's what confused me I thought "oh maybe it's because the property set accessor is private" but then my other properties have private set accessors too and they get written to just fine, also, I read some where that reflection doesn't care about property accessor level, as long as the property itself is public it can get to it. – Daniel Robinson Sep 14 '12 at 21:07
  • Is the `Revision` property on the `Product` class or the `MongoDoc` class? – Dylan Meador Sep 14 '12 at 21:32
  • the MongoDoc class, that's what I tried to say in the question, but it wasn't very clear sorry. But then the updateSelf method is in the MongoDoc class and the parameter is a mongoDoc as is `this` obviously – Daniel Robinson Sep 14 '12 at 21:34
  • Nothing special is needed. What exactly is the exception? Include the InnerException. – Hans Passant Sep 14 '12 at 21:45
  • Do you possibly need to use reflection on the base type (`MongoDoc` in this case) to access the setter? See [here](http://stackoverflow.com/q/686482/684831) – Dylan Meador Sep 14 '12 at 21:46

4 Answers4

2

I fixed it with this, thanks to Dylan Meador for pointing me to another question which gave me enough to get the solution:

    private void updateSelf(MongoDoc newDoc, Type type)
    {
        foreach (var i in type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
        {
            if (i.GetCustomAttributes(false).Any(x => x is MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute)) continue;
            Object oldValue = i.GetValue(this, null);
            Object newValue = i.GetValue(newDoc, null);
            if (!Object.Equals(oldValue, newValue) && !((oldValue == null) && (newValue == null)))
            {
                i.SetValue(this, newValue, null);
            }
        }
        Type baseType = type.BaseType;
        if (baseType != null)
        {
            this.updateSelf(newDoc, baseType);
        }
    }

It looks like the Type needed to be explicitly set to the base class type in order to use the set accessor for that particular property.

Daniel Robinson
  • 13,806
  • 18
  • 64
  • 112
  • 1
    The reason you must use the base type is because the set accessor is private. With `class A {}; class B : A {}`, B does not have access to A's private members, and so reflection also does not allow you to access A's private members through type B. – phoog Sep 14 '12 at 22:47
  • Indeed, that was the exact case in my situation. I got the DeclaringType of property and was able to set the property. var propertyInfo = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty).FirstOrDefault(); var p2 = propertyInfo.DeclaringType.GetProperty(propertyInfo.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty); p2.SetValue(instance, messageHandler, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty, null, null, null); – Puterdo Borato Nov 07 '13 at 21:12
1

Try using the overload of SetValue that has an System.Reflection.BindingFlags parameter and pass it a value of BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • tried it with `i.SetValue(this, newValue, System.Reflection.BindingFlags.SetProperty | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic, null, null, null);` but to no avail the exact same problem occurs – Daniel Robinson Sep 14 '12 at 21:15
0

The problem is that from the point of view of the derived type there is no setter for the property.

You can work around this walking the inheritance hierarchy and getting the properties declared on each type (using the DeclaredOnly BindingFlag, together with Public and Instance), or checking whether the property is declared by the reflection type and if not get it again from the property info's DeclaringType.

For the former you'd you could have a nested loop, while the latter would look something like this:

    foreach (var property in p.GetType().GetProperties())
    {
        var actualProperty = property.DeclaringType != property.ReflectedType ? property.DeclaringType.GetProperty(property.Name) : property;
        actualProperty.SetValue(p, newValue, null);
    }
fsimonazzi
  • 2,975
  • 15
  • 13
  • Why not just use property.DeclaringType always? – phoog Sep 14 '12 at 22:48
  • You mean rather than checking whether it's different to property.ReflectedType? They are equivalent really. – fsimonazzi Sep 17 '12 at 18:24
  • I meant why use the conditional operator at all? It's simpler to say `var actualProperty = property.DeclaringType.GetProperty(property.Name);` rather than `var actualProperty = property.DeclaringType != property.ReflectedType ? property.DeclaringType.GetProperty(property.Name) : property;`. – phoog Sep 19 '12 at 16:18
  • Yes, got it. The result is the same. – fsimonazzi Sep 19 '12 at 19:32
0

Probably the issue can be in next:
You are using auto property
public uint Revision { get; private set; } Which perform boxing/unboxing operations through default type int. So, here you should explicitly cast to uint type.

You can find out similar problem with next snippet:

byte b1 = 4;  
byte b2 = 5;  
byte sum = b1 + b2;

This will raise an exception since no overload are declared for "+" operator. This snippet actually demonstrate an issue with boxing/unboxing operations through default type int.