1

I have a model with a bunch of properties in Cassandra and C#, so to prevent myself having to assign manually every value from a normal Prepared Statement (or a script that does it for me), I started using the Mapper Component. However, CQL dates are automatically mapped to LocalDates which are serialized into a JSON object with Date, Month and Year properties and I need a normal string.

I gave a look into MappingConfiguration.Global which has the Define and ConvertTypesUsing methods, but the first seems to not have what I'm looking for and I wasn't even able to implement the TypeConverter class it expects as a parameter on the latter, since it has a syntax I've never seen (and I don't even know if it does the job).

So I was wondering, are there any built in options in the CassandraDrive to achieve what I want?

If anyone wants to give a look to my custom TypeConverter, this what I thought would work:

using System;
using Cassandra;
using Cassandra.Mapping.TypeConversion;

namespace Api.Helpers
{
    public class LocalDateConverter : TypeConverter
    {
        protected override Func<TDatabase, TPoco> GetUserDefinedFromDbConverter<TDatabase, TPoco>()
        {
            Func<LocalDate, string> converter = date => 
                String.Format("{0}-{1}-{2}", date.Year, date.Month, date.Year);
            return converter;
        }

        protected override Func<TPoco, TDatabase> GetUserDefinedToDbConverter<TPoco, TDatabase>()
        {
            throw new NotImplementedException();
        }
    }

With that method declaration syntax I don't know where I'm supposed to indicate what types I want the converter to apply. This code fails with Can't convert 'System.Func<Cassandra.LocalDate, string>' into 'System.Func<TDatabase, TPoco>'.

Another solution non related to the Cassandra driver was a custom Json Converter, but I'm also using OData, which uses a custom serializer.

Juan De la Cruz
  • 436
  • 6
  • 17

1 Answers1

2

Try this:

public class CustomTypeConverter : TypeConverter
{
    protected override Func<TDatabase, TPoco> GetUserDefinedFromDbConverter<TDatabase, TPoco>()
    {
        if (typeof(TDatabase) == typeof(LocalDate) && typeof(TPoco) == typeof(string))
        {
            Func<LocalDate, string> func = date => date?.ToString();
            return (Func<TDatabase, TPoco>) (object) func;
        }

        return null;
    }

    protected override Func<TPoco, TDatabase> GetUserDefinedToDbConverter<TPoco, TDatabase>()
    {
        if (typeof(TDatabase) == typeof(LocalDate) && typeof(TPoco) == typeof(string))
        {
            Func<string, LocalDate> func = dateStr => dateStr == null ? null : LocalDate.Parse(dateStr);
            return (Func<TPoco, TDatabase>) (object) func;
        }

        return null;
    }
}

You can provide a custom converter to the Mapper or LINQ via the MappingConfiguration object:

var mc = new MappingConfiguration().ConvertTypesUsing(new CustomTypeConverter());

// LINQ
var table = new Table<Poco>(session, mc);

// Mapper
var mapper = new Mapper(session, mc);

For this to work you have to change your property type to string and tell the driver that the property maps to a LocalDate column:

public class Poco
{
    [Cassandra.Mapping.Attributes.PartitionKey]
    [Cassandra.Mapping.Attributes.Column("project_type", Type = typeof(int))]
    public ProjectType ProjectType { get; set; }
    
    [Cassandra.Mapping.Attributes.Column("date", Type = typeof(LocalDate))]
    public string Date { get; set; }
}

If you don't use attribute based config, it looks like this:

var mc = new MappingConfiguration()
            .ConvertTypesUsing(new CustomTypeConverter())
            .Define(
                new Map<Poco>()
                    .PartitionKey(s => s.ProjectType)
                    .Column(s => s.ProjectType, c => c.WithName("project_type"))
                    .Column(s => s.Date, c => c.WithDbType<LocalDate>()));

You also need to define your mappings in this MappingConfiguration instance instead of MappingConfiguration.Global (unless you're using attribute based config).

Just make sure to always use the same MappingConfiguration instance instead of creating multiple ones because this is where the driver stores the generated prepared statements.

João Reis
  • 712
  • 3
  • 9