SafeHandle cannot be null


#1

Payments have a PaymentMethod (VISA, MasterCard, Cash, Cheque, Other etc). Users can manage the payment methods they accept. If a user chooses to delete a payment method, any payments made using that method are set to the the default payment method, Other.

A payment method looks like this

internal class PaymentMethod : RealmObject
{
    [PrimaryKey]
    public string PaymentMethodId { get; set; }
    [Required]
    public string PaymentCode { get; set; }
    [Required]
    public string PaymentMethodName { get; set; }

    [Backlink(nameof(Payment.PaymentMethod))]
    internal IQueryable<Payment> Payments { get; }

    public override string ToString() => PaymentCode;
}

and a payment looks like this

internal class Payment : RealmObject
{
    [PrimaryKey]
    public string PaymentId { get; set; }
    public Order Order { get; set; }
    public PaymentMethod PaymentMethod { get; set; }
    public StaffMember StaffMember { get; set; }
    public DateTimeOffset PaymentDate { get; set; }
    public double PaymentAmount { get; set; }
}

I am trying to update payments that use payment methods that are to be removed like this.

foreach (PaymentMethod r in removes)
{
    IQueryable<Payment> updates = realm.All<Payment>().Where(e => e.PaymentMethod == r);
    foreach (Payment payment in updates)
        payment.PaymentMethod = defaultPaymentMethod;
}

The line foreach (Payment payment in updates) throws

SafeHandle cannot be null.

The full stack trace is

   at System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument, ExceptionResource resource)
   at System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle, Boolean& success)
   at Realms.QueryHandle.NativeMethods.query_object_equal(QueryHandle queryPtr, IntPtr columnIndex, ObjectHandle objectHandle, NativeException& ex)
   at Realms.RealmResultsVisitor.AddQueryEqual(QueryHandle queryHandle, String columnName, Object value, Type columnType)
   at Realms.RealmResultsVisitor.VisitBinary(BinaryExpression node)
   at Realms.RealmResultsVisitor.VisitMethodCall(MethodCallExpression node)
   at Realms.RealmResults`1.CreateHandle()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Lazy`1.CreateValue()
   at Realms.RealmCollectionBase`1.get_IsValid()

#2

It appears there’s a corner case that isn’t gracefully handled in the our IQueryable implementation - we don’t check if the object you’re comparing to is managed. My guess is that r is not a managed RealmObject - do you first remove the payment methods or do you first set the payment’s payment method to default?


#3

You are correct @nirinchev. removes were not managed. I’ve moved the creation of the list of removes to the same method and the issue went away.

but …

now, after successfully updating payments with the default payment method I delete the unwanted payment methods like this

IQueryable<PaymentMethod> removes = removes.AsQueryable();
realm.RemoveRange(removes);

and get

An exception of type ‘System.ArgumentException’ occurred in Realm.dll but was not handled in user code range should be the return value of .All or a LINQ query applied to it.


#4

Yeah - you can’t convert a normal collection to a Realm collection (which is what the RemoveRange method expects). It’s designed to work with managed Realm queries, e.g.:

var query = realm.All<Foo>().Where(f => f.Bar > 5);
realm.RemoveRange(query);

In your case you’ll just have to manually remove them one by one:

foreach (var remove in removes)
{
    realm.Remove(remove);
}

Hope this helps.


#5

We now have error free update and deletion from a list. Thanks once again for your help @nirinchev.