Search code examples
c#elasticsearchnest

Elasticsearch(C# nest): Map abstract property


I have a class ProductDiscount in C#. Other classes inherit from it (FlatDiscount, PercentageDiscount etc).

Storing data in Elastic seems to work but I can't read data from Elastic.

I get this error: Could not create an instance of type ProductDiscount. Type is an interface or abstract class and cannot be instantiated. Path 'discount.amount', line 1, position 1098.

When I look at my index the discount is indeed storef in the collection but there's no indication of the type of the class.

Is it possible to map an abstract property in c# with the nest client?

I've tried with this mapping descriptor but with no succes:

mappingsDescriptor.Map<Product>(x => x
                .Properties(props => props

                    .Object<ProductDiscount>(o => o.Name(prop => prop.Discount))
                    .Object<FlatProductDiscount>(o => o.Name(prop => prop.Discount).AutoMap())
                    .Object<PercentageProductDiscount>(o => o.Name(prop => prop.Discount).AutoMap())
                    .Object<FreeProductProductDiscount>(o => o.Name(prop => prop.Discount).AutoMap())
                    .Object<QuantityProductDiscount>(o => o.Name(prop => prop.Discount).AutoMap())
                 )
            );

Reading is done with the ElasticClient:

    var result = await ElasticClient.SearchAsync<Product>(new SearchRequest(Indices.Index(index: CollectionName)));

Solution

  • I found a solution!!

    I wrote a custom converter using the JsonSubTypes package.

        private static JsonConverter DiscountConverter()
        {
            var assembly = Assembly.GetAssembly(typeof(ProductDiscount));
    
            var builder = JsonSubtypesConverterBuilder
                .Of(typeof(ProductDiscount), "Type");
    
            assembly
                .GetTypes()
                .Where(type => type.IsSubclassOf(typeof(ProductDiscount)) && !type.IsAbstract)
                .ForEach(s =>
                {
                    builder.RegisterSubtype(s, s.Name);
                });
    
            var converter = builder
                .SerializeDiscriminatorProperty()
                .Build();
    
            return converter;
        } 
    

    My connection is set up like this

                var pool = new SingleNodeConnectionPool(new Uri(uris.First()));
                connectionSettings = new ConnectionSettings(pool, connection, SourceSerializer());
    
        private static ConnectionSettings.SourceSerializerFactory SourceSerializer()
        {
            return (builtin, settings) => new JsonNetSerializer(builtin, settings,
                () => new JsonSerializerSettings
                {
                    Converters = new List<JsonConverter>
                    {
                        new StringEnumConverter(),
                        DiscountConverter()
                    }
                });
        }