EntityFrameworkCore.ClickHouse icon indicating copy to clipboard operation
EntityFrameworkCore.ClickHouse copied to clipboard

TimeOnly support (possibly an issue related to arrays of tuples?)

Open TPCharts opened this issue 6 months ago • 1 comments

I'm trying to store the following field:

public required Tuple<TimeOnly, EventImpact>[] EventTimesAndImpacts { get; set; }

TimeOnly is .NET's TimeOnly.

EventImpact is my enum.

Attempting to interact with the database (in this case, context.Database.EnsureDeleted(); in a unit test) throws the following exception:

System.NullReferenceException: 'Object reference not set to an instance of an object.'

... at line 256 in ClickHouseTypeMappingSource.FindTupleMapping(in RelationalTypeMappingInfo mappingInfo)

private RelationalTypeMapping FindTupleMapping(in RelationalTypeMappingInfo mappingInfo)
{
    if (mappingInfo.ClrType is not { IsGenericType: true })
    {
        return null;
    }

    var genericTypeDefinition = mappingInfo.ClrType.GetGenericTypeDefinition();

    if (
        genericTypeDefinition == typeof(Tuple<>)
        || genericTypeDefinition == typeof(Tuple<,>)
        || genericTypeDefinition == typeof(Tuple<,,>)
        || genericTypeDefinition == typeof(Tuple<,,,>)
        || genericTypeDefinition == typeof(Tuple<,,,,>)
        || genericTypeDefinition == typeof(Tuple<,,,,,>)
        || genericTypeDefinition == typeof(Tuple<,,,,,,>)
        || genericTypeDefinition == typeof(Tuple<,,,,,,,>)
    )
    {
        var genericArguments = mappingInfo.ClrType.GetGenericArguments();
        var storeType =
            "tuple("
            + string.Join(
                ", ",
                genericArguments.Select(e =>
                    // ⬇️⬇️ This line throws the exception
                    this.FindMapping(new RelationalTypeMappingInfo(e))!.StoreType
                )
            )
            + ")";
        return new ClickHouseTupleTypeMapping(storeType, mappingInfo.ClrType, this);
    }

    return null;
}

I'm guessing that the problem is that TimeOnly isn't mapped to a ClickHouse type.

Other related information worth mentioning:

  1. Using the shorter (TimeOnly, EventImpact) tuple syntax throws exceptions that the mapping isn't found. Using Tuple<TimeOnly, EventImpact> with a custom ValueConverter seems to proceed past the first few mapping-not-found family of exceptions.

  2. While trying to resolve the issue in the just-mentioned item 1, I added the following converter:

public class TimeAndImpactTupleConverter  : ValueConverter<Tuple<TimeOnly, EventImpact>[], Tuple<TimeOnly, int>[]>
{
    public TimeAndImpactTupleConverter() : base(
        v => v.Select(x => Tuple.Create(x.Item1, (int)x.Item2)).ToArray(),
        v => v.Select(x => Tuple.Create(x.Item1, (EventImpact)x.Item2)).ToArray() { }
}

... and registered it in the DbContext:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder
         .Properties<(TimeOnly, EventImpact)[]>()
         .HaveConversion<TimeAndImpactTupleConverter>();
}

Here's the property in the table's DTO class:

public abstract class StatsTimeDtoBase : StatsModelDtoBase
{
    public required Tuple<TimeOnly, EventImpact>[] EventTimesAndImpacts { get; set; }
}

I don't know how to proceed or what exactly is going wrong (not very familiar with EF internals).

Are there any quick fix or workaround suggestions, or a better approach to what I'm trying to do (store a list of key-value pairs inside a row - denormalized is significantly preferred, I think, unlikely to have more than 20 or so variations of this field's values - for queries that need to filter on TimeOnly paired with an int/enum)?

Previously I was trying to store these tuples of TimeOnly and EventImpact as strings, but that wasn't successful due to the use case (sometimes I need to filter on TimeOnly paired with a specific EventImpact, other times only need to filter on TimeOnly).

I'm using a cloned version of this repository, so could implement or test things quickly for you without a full NuGet release.

Thanks!


Note: edited for additional information at 2024-08-09 17:31 UTC

TPCharts avatar Aug 09 '24 17:08 TPCharts