EntityFrameworkCore.ClickHouse
EntityFrameworkCore.ClickHouse copied to clipboard
TimeOnly support (possibly an issue related to arrays of tuples?)
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:
-
Using the shorter
(TimeOnly, EventImpact)
tuple syntax throws exceptions that the mapping isn't found. UsingTuple<TimeOnly, EventImpact>
with a customValueConverter
seems to proceed past the first few mapping-not-found family of exceptions. -
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