Understanding [Required]

We try to intercept invalid data in our property setters:

internal class OrderDetail : RealmObject
{
    [PrimaryKey]
    [MapTo(nameof(OrderDetailId))]
    private string _OrderDetailId { get; set; } = Guid.NewGuid().ToString();
    public string OrderDetailId
    {
        get => _OrderDetailId;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new Exception("OrderDetailId cannot be null");
            _OrderDetailId = value;
        }
    }
    [MapTo(nameof(Order))]
    private Order _Order { get; set; }
    public Order Order
    {
        get => _Order;
        set => _Order = value ?? throw new Exception("Order cannot be null for OrderDetail");
    }

    [Required]
    [MapTo(nameof(ItemCode))]
    private string _ItemCode { get; set; }
    public string ItemCode
    {
        get { return _ItemCode; }
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new Exception("ItemCode cannot be blank");
            _ItemCode = value;
        }
    }
}

In testing if we try ItemCode = null it throws

ItemCode cannot be blank

as expected. Our property setter validation is kicking in before the [Required] attribute.

However we are seeing error reports with the exception.

ObjectHandle.SetString (IntPtr, String)

+ 0x, line 86

Realms.Exceptions.RealmException: Column is not nullable

Stack trace:

Realms
ObjectHandle.SetString (IntPtr, String) + 0x:86
Realms
RealmObject.SetStringValue (String, String) + 0x:59
Wibble.Data.OrderDetail
RealmHelper.CopyToRealm (RealmObject, Boolean, Boolean) + 0xe:7
Realms
Realm.Write (Action) + 0x:65
Wibble.Services.OrderService
<SaveOrderAsync>d__24.MoveNext () + 0x36c

This appears to be the [Required] attribute kicking in. The error message is unhelpful for a user. How is it that validation on the setter is bypassed?

When adding a standalone object to the Realm, we only enumerate the properties that Realm knows about (i.e. we don’t know ItemCode is the public property for _ItemCode). Also, when you create a new instance of the OrderDetail class, all properties have their default values (i.e. null for strings). If you want to prevent people from creating an instance and then directly adding it to the realm without going through your custom properties first, you can either declare a private parameterless constructor that Realm can use and a public constructor that takes all properties that must be set before the object can be added to the Realm, or you can supply default values for the Realm properties that are not null.

Thank you for the explanation @nirinchev. Just so that I have it clear in my head, in our OrderDetail
Realm knows about _ItemCode
Realm does not know about ItemCode.
A new OrderDetail can be created with ItemCode not set. Therefore validation on the property would not trigger and [Required] would trigger when there was an attempt to persist the OrderDetail.

Is that correct?

Correct. If you do

var myOrder = new OrderDetail();

realm.Write(() => realm.Add(myOrder));

You would not touch any of your validation code, but the properties would still be null, which will trigger an error when adding it to Realm.

1 Like