Applying conventions through the Visitor pattern

edit

It is also possible to apply a transformation on all or specific properties.

.AutoMap() internally implements the visitor pattern. The default visitor, NoopPropertyVisitor, does nothing and acts as a blank canvas for you to implement your own visiting methods.

For instance, let’s create a custom visitor that disables doc values for numeric and boolean types (Not really a good idea in practice, but let’s do it anyway for the sake of a clear example.)

Using the following two POCOs as in previous examples,

public class Company
{
    public string Name { get; set; }
    public List<Employee> Employees { get; set; }
}

public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Salary { get; set; }
    public DateTime Birthday { get; set; }
    public bool IsManager { get; set; }
    public List<Employee> Employees { get; set; }
    public TimeSpan Hours { get; set; }
}

We first define a visitor; it’s easiest to inherit from NoopPropertyVisitor and override the Visit methods to implement your conventions

public class DisableDocValuesPropertyVisitor : NoopPropertyVisitor
{
    public override void Visit(
        INumberProperty type,
        PropertyInfo propertyInfo,
        ElasticsearchPropertyAttributeBase attribute) 
    {
        type.DocValues = false;
    }

    public override void Visit(
        IBooleanProperty type,
        PropertyInfo propertyInfo,
        ElasticsearchPropertyAttributeBase attribute) 
    {
        type.DocValues = false;
    }
}

Override the Visit method on INumberProperty and set DocValues = false

Similarily, override the Visit method on IBooleanProperty and set DocValues = false

Now we can pass an instance of our custom visitor to .AutoMap()

var createIndexResponse = client.CreateIndex("myindex", c => c
    .Mappings(ms => ms
        .Map<Employee>(m => m.AutoMap(new DisableDocValuesPropertyVisitor()))
    )
);

and any time the client maps a property of the POCO (Employee in this example) as a number (INumberProperty) or boolean (IBooleanProperty), it will apply the transformation defined in each Visit() call respectively, which in this example disables doc_values.

{
  "mappings": {
    "employee": {
      "properties": {
        "birthday": {
          "type": "date"
        },
        "employees": {
          "properties": {},
          "type": "object"
        },
        "firstName": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "isManager": {
          "doc_values": false,
          "type": "boolean"
        },
        "lastName": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "salary": {
          "doc_values": false,
          "type": "integer"
        },
        "hours": {
          "doc_values": false,
          "type": "long"
        }
      }
    }
  }
}

Visiting on PropertyInfo

edit

You can even take the visitor approach a step further, and instead of visiting on IProperty types, visit directly on your POCO reflected PropertyInfo properties.

As an example, let’s create a visitor that maps all CLR types to an Elasticsearch text datatype (ITextProperty).

public class EverythingIsATextPropertyVisitor : NoopPropertyVisitor
{
    public override IProperty Visit(PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) => new TextProperty();
}

var createIndexResponse = client.CreateIndex("myindex", c => c
        .Mappings(ms => ms
            .Map<Employee>(m => m.AutoMap(new EverythingIsATextPropertyVisitor()))
        )
    );
{
  "mappings": {
    "employee": {
      "properties": {
        "birthday": {
          "type": "text"
        },
        "employees": {
          "type": "text"
        },
        "firstName": {
          "type": "text"
        },
        "isManager": {
          "type": "text"
        },
        "lastName": {
          "type": "text"
        },
        "salary": {
          "type": "text"
        },
        "hours": {
          "type": "text"
        }
      }
    }
  }
}

Skip properties

edit

Through implementing SkipProperty on the visitor, you can prevent certain properties from being mapped.

In this example, we skip the inherited properties of the type from which DictionaryDocument is derived

public class DictionaryDocument : SortedDictionary<string, dynamic>
{
    public int Id { get; set; }
}

public class IgnoreInheritedPropertiesVisitor<T>  : NoopPropertyVisitor
{
    public override bool SkipProperty(PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute)
    {
        return propertyInfo?.DeclaringType != typeof(T);
    }
}

var createIndexResponse = client.CreateIndex("myindex", c => c
        .Mappings(ms => ms
            .Map<DictionaryDocument>(m => m.AutoMap(new IgnoreInheritedPropertiesVisitor<DictionaryDocument>()))
        )
    );
{
  "mappings": {
    "dictionarydocument": {
      "properties": {
        "id": {
          "type": "integer"
        }
      }
    }
  }
}