Aaron B
Aaron B

Reputation: 1041

Azure Search SDK null field not set on Merge action

I'm using Microsoft.Azure.Search version 3.0.1,

I'm trying the following:

// subset of my index's fields
private class SyncFields
{   
    public string Id { get; set; }
    public DateTimeOffset? ApprovedOn { get; set; }
    public DateTimeOffset? IgnoredOn { get; set; }
}

public void Sync()
{
    var sync = new SyncFields
    {
        Id = "94303",
        ApprovedOn = null,
        IgnoredOn = DateTime.UtcNow
    };

    var searchClient = new SearchServiceClient("xxxx",
        new SearchCredentials("xxxx"));
    searchClient.SerializationSettings.NullValueHandling = NullValueHandling.Include;

    using (var client = searchClient.Indexes.GetClient("xxxx"))
    {
        client.SerializationSettings.NullValueHandling = NullValueHandling.Include;
        var batch = IndexBatch.Merge<SyncFields>(new[] { sync });
        client.Documents.Index<SyncFields>(batch);
    }
}

This isn't settings ApprovedOn to null. It ignores it. If I set a non-null value, it does set it.

According to the documentation here the merge operation updates the field to be null. And in fact, if I make this Http post request manually with JSON, this is true. But the SDK isn't updating the field(s) to null. What am I missing?

Upvotes: 4

Views: 1470

Answers (2)

Aaron B
Aaron B

Reputation: 1041

I found the culprit in the Azure Search SDK source.

Line 51, settings.NullValueHandling = NullValueHandling.Ignore; is overriding the setting I tried to set. I will probably be making an issue about this in Github.

For now, I am using a custom converter as a workaround.

public class DefaultDateTimeOffsetIsNullConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(DateTimeOffset?));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var date = (DateTimeOffset?)value;
        if (date == default(DateTimeOffset))
        {
            writer.WriteNull();
        }
        else
        {
            writer.WriteValue(date);
        }
    }

    public override bool CanRead => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

As in

var sync = new SyncFields
{
    Id = "94303",
    ApprovedOn = default(DateTimeOffset), // set to null
    IgnoredOn = DateTime.UtcNow
};

// ...

client.SerializationSettings.Converters.Add(new DefaultDateTimeOffsetIsNullConverter());

// ...

Edit:

Two other superior options listed by Bruce: using Document which is untyped, and using the JsonPropertyAttribute on the field to get the correct serialization. Using Document is ideal for my use case, no serialization problem or custom converters:

var sync = new Document
{
    ["Id"] = "94303",
    ["ApprovedOn"] = null,
    ["IgnoredOn"] = null
};

// ... the same as before:
var batch = IndexBatch.Merge(new[] { sync });
await client.Documents.IndexAsync(batch);

Upvotes: 2

Bruce Johnston
Bruce Johnston

Reputation: 8634

This is a known limitation of the typed overloads of the Index family of methods. The issue is described in detail here: https://github.com/Azure/azure-sdk-for-net/issues/1804

Some workarounds:

  1. Use the untyped version of Index instead for merge scenarios.
  2. Use Upload instead of Merge.
  3. Put [JsonProperty(NullValueHandling = NullValueHandling.Include)] on the properties of your model class that you need to explicitly set to null in a merge operation (not recommended if you have many fields in your index).
  4. Implement a custom converter.

Upvotes: 4

Related Questions