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 descriptor = new CreateIndexDescriptor("myindex")
    .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": "string"
        },
        "hours": {
          "doc_values": false,
          "type": "long"
        },
        "isManager": {
          "doc_values": false,
          "type": "boolean"
        },
        "lastName": {
          "type": "string"
        },
        "salary": {
          "doc_values": false,
          "type": "integer"
        }
      }
    }
  }
}

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 string datatype (IStringProperty).

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

var descriptor = new CreateIndexDescriptor("myindex")
        .Mappings(ms => ms
            .Map<Employee>(m => m.AutoMap(new EverythingIsAStringPropertyVisitor()))
        );
{
  "mappings": {
    "employee": {
      "properties": {
        "birthday": {
          "type": "string"
        },
        "employees": {
          "type": "string"
        },
        "firstName": {
          "type": "string"
        },
        "isManager": {
          "type": "string"
        },
        "lastName": {
          "type": "string"
        },
        "salary": {
          "type": "string"
        },
        "hours": {
          "type": "string"
        }
      }
    }
  }
}