Unable To Use Reflection On Unmanaged Object

Is there any plan/hope we can use reflection against unmanaged objects? I’m not sure I understand the purpose of restricting this. I have a Realm object I don’t need to or want to save to the Realm database. I use it for holding interactions within the UI. What harm is there in using reflection against these objects?

I added it to my Realm database as a hack to get around this limitation. This object is bound to a grid in my UI which normally Realm will handle wrapping write transactions around changes through the UI. But, once I add the object to Realm to get around the restriction above, if I change a property on said object, it throws the all-too-familiar exception “cannot modify Realm object outside of a write transaction”.

I guess I’ll continue to look for another hack to get around this unless someone knows a way to get around the restriction of using reflection against an unmanaged object.

I do have the ValidateNever attribute on this class to get around serialization of my objects because of the Dynamic API stuff. That’s another one I don’t understand why this forced limitation. At least I can use that attribute to get around this one. I was hoping for another one for this reflection restriction.

I’m writing in Xamarin, c#. App is running in UWP now, but will eventually run on Android and iOS. Latest version of everything.

Thoughts?

Thanks!

Can you clarify what exactly is the issue when using reflection? As far as I’m aware, there’s no limitation imposed by Realm there. If you can provide code samples of what you tried and error messages that you got, it’ll make it a lot easier to help.

Thanks for responding!

If you create a Realm object, don’t add it to your Realm database and try to use reflection against it, such as getting property values from it, you’ll receive this error message:

Using the dynamic API to access a RealmObject is only possible for managed (persisted) objects.

Sorry I didn’t post code before. I read another blog post on this forum talking about this limitation so I assumed it was well-known. I’m not doing anything fancy. Just trying to dynamically pull property values from unmanaged Realm objects. Here’s a snippet showing what I’m doing with reflection.

Note: I added the Database.Write and Database.Add as a hack to try to get around it which it makes this code work, but then it fails in the UI like I said in my original post. If I take that Write transaction out and don’t add the 2 objects to the Database, it will blow up on the line “var oldValue = property.GetValue(oldPricingPoint);” with the error above.

private void StagePricePointChanges(PricingPoint pricingPoint, CRUDActions action)
{
PricingPoint oldPricingPoint = null;
if (Guid.TryParse(pricingPoint._id, out Guid id))
{
StagedCategorySettingsChanges stagedChange = StagedChanges.Where(item => item.ChangedCategory._id == SelectedCategory._id)?.SingleOrDefault();
if (stagedChange != null)
{
oldPricingPoint = stagedChange.PricingPointChangesRealm.Where(item => item.OldPricePoint?._id == pricingPoint._id).Select(item => item.OldPricePoint)?.SingleOrDefault();
}
if (oldPricingPoint == null)
{
oldPricingPoint = InstantDataAccess.GetInventoryPOS(id);
}
}
bool hasGlobalChanges = false;
bool hasLocalChanges = false;
List propertyNamesToIgnore = new List()
{
{ nameof(PricingPoint._id) },
{ nameof(PricingPoint.CreatedOnTicks) },
{ nameof(PricingPoint.LastUpdatedTicks) },
{ nameof(PricingPoint.NeedsSynced) }
};

        PropertyInfo[] properties = typeof(PricingPoint).GetProperties();
        App.Database.Write(() =>
        {
            App.Database.Add(oldPricingPoint, true);//HACK: this is required so I can use reflection against a Realm object. 
            App.Database.Add(pricingPoint, true);//HACK: this is required so I can use reflection against a Realm object. 
            if (properties != null && properties.Length > 0)
            {
                foreach (PropertyInfo property in properties.Where(item => !propertyNamesToIgnore.Contains(item.Name)))
                {
                    var oldValue = property.GetValue(oldPricingPoint);
                    var newValue = property.GetValue(pricingPoint);
                    if ((oldValue == null && newValue != null) || (oldValue != null && newValue == null) || (oldValue != null && newValue != null && !oldValue.Equals(newValue)))
                    {
                        if (property.CustomAttributes.Any(item => item.AttributeType == typeof(IsGlobalDefaultAttribute)))
                        {
                            hasGlobalChanges = true;
                        }
                        else
                        {
                            hasLocalChanges = true;
                        }
                        if (hasGlobalChanges && hasLocalChanges)
                        {
                            break;
                        }
                    }
                }
            }
            StagedCategorySettingsChanges change = StageChanges(nameof(Category.PricingPointIds), hasGlobalChanges: hasGlobalChanges, hasLocalChanges: hasLocalChanges);

            StagedPricePointChange pricingPointChange = change.PricingPointChangesRealm.Where(item => item.NewPricePoint._id == pricingPoint._id && item.Action == action)?.SingleOrDefault();

            if (pricingPointChange == null)
            {
                pricingPointChange = new StagedPricePointChange() { ActionInt = (int)action };
                pricingPointChange.OldPricePoint = oldPricingPoint;
                change.PricingPointChangesRealm.Add(pricingPointChange);
            }
            pricingPointChange.NewPricePoint = pricingPoint;

            switch (action)
            {
                case CRUDActions.Create:
                case CRUDActions.Update:
                    pricingPoint.PropertyChanged -= PricingPoint_PropertyChanged;
                    pricingPoint.PropertyChanged += PricingPoint_PropertyChanged;
                    break;
                case CRUDActions.Delete:
                    pricingPoint.PropertyChanged -= PricingPoint_PropertyChanged;
                    break;
                case CRUDActions.Read:
                case CRUDActions.Unknown:
                default:
                    break;
            }
        });
    }
1 Like

The issue here is not the use of reflection, but rather trying to access the DynamicApi property on RealmObjectBase which throws this error. It would throw the same error if you tried to access is statically.

To avoid that, pass BindingFlags.DeclaredOnly (SO answer for reference) to your call to GetProperties. This will give you only properties declared on your model class and not base class properties, such as DynamicApi, Realm, and so on.

2 Likes

That fixed it. Thanks so much!

I misunderstood your post on this topic. I see now what you’re saying is you can’t access DynamicAPI properties only via reflection for unmanaged objects. That helps a lot. As I said above, I couldn’t figure out why Realm would limit reflection.

https://www.mongodb.com/community/forums/t/realm-10-2-0-exception/112519

"Hm, that’s interesting - it seems like MVC is trying to access the DynamicApi property through reflection but that’s not something that will not work for unmanaged objects.

I’ll need to research how, but there’s probably a way to tell MVC to ignore some properties."

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.