Hello, I’m using the Cassandra C# driver in applications published with .NET NativeAOT. At the moment, the driver crashes during connection initialization because some generic types are instantiated dynamically through reflection, and the required metadata is not preserved by AOT compilation.
The exception I’m seeing is the following:
Application: MyApp.exe
CoreCLR Version: 10.0.8
Description: The process was terminated due to an unhandled exception.
Exception Info: Cassandra.NoHostAvailableException: All hosts tried for query failed (tried 11.111.11.11:1111: NotSupportedException 'Reflection_InsufficientMetadata_EdbNeeded, System.Collections.Generic.SortedDictionary`2[System.Guid,System.Byte[]]'; 11.111.11.11:1111: NotSupportedException 'Reflection_InsufficientMetadata_EdbNeeded, System.Collections.Generic.SortedDictionary`2[System.Guid,System.Byte[]]'; ...), see Errors property for more info
at Cassandra.Connections.Control.ControlConnection.<Connect>d__44.MoveNext() + 0x14b6
--- End of stack trace from previous location ---
at Cassandra.Connections.Control.ControlConnection.<InitAsync>d__35.MoveNext() + 0x6e
--- End of stack trace from previous location ---
at Cassandra.Tasks.TaskHelper.<WaitToCompleteAsync>d__10.MoveNext() + 0x18b
--- End of stack trace from previous location ---
at Cassandra.Cluster.<Init>d__40.MoveNext() + 0x9b0
--- End of stack trace from previous location ---
at Cassandra.Cluster.<ConnectAsync>d__50.MoveNext() + 0xfa
--- End of stack trace from previous location ---
at Cassandra.Tasks.TaskHelper.WaitToComplete(Task, Int32) + 0xb3
at MyAppCore.CassandraAccessor..ctor(String, String, String, String, String) + 0x11e
at MyAppCore.DocumentAccessorFactory.CreateFromParam(String, Dictionary`2) + 0x212
at MyApp.MainWindow.<ServerTree_SelectionChanged>g__GetOrCreateDocBrowser|5_0(ServerOptions) + 0x74
at MyApp.MainWindow.<ServerTree_SelectionChanged>d__5.MoveNext() + 0x151
--- End of stack trace from previous location ---
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__124_0(Object state) + 0x1a
at Avalonia.Threading.SendOrPostCallbackDispatcherOperation.InvokeCore() + 0x19a
at Avalonia.Threading.DispatcherOperation.Execute() + 0x4a
at Avalonia.Threading.Dispatcher.ExecuteJob(DispatcherOperation) + 0x78
at Avalonia.Threading.Dispatcher.ExecuteJobsCore(Boolean) + 0x387
at Avalonia.Win32.Win32Platform.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam) + 0x58
at MyApp!<BaseAddress>+0xfc025a
From my investigation, the issue happens when the driver dynamically creates instances of SortedDictionary<TKey, TValue> for types that were not statically referenced anywhere in the application. Since NativeAOT trims unused metadata, those generic instantiations are unavailable at runtime.
As a workaround, I was able to make the driver work by explicitly referencing the required concrete generic types in my application:
_= new Dictionary<(Type, Type), Type>
{
[(typeof(Guid), typeof(byte[]))] = typeof(SortedDictionary<Guid, byte[]>),
[(typeof(string), typeof(string))] = typeof(SortedDictionary<string, string>),
};
With this workaround in place, basic driver usage works correctly in NativeAOT mode:
var cluster = Cluster.Builder()/*...*/.Build();
var session = cluster.Connect(keyspace);
var cql = new SimpleStatement("SELECT * FROM system_schema.columns WHERE keyspace_name = 'foobar';");
var result = await _session.ExecuteAsync(cql).ConfigureAwait(false);
var tableColumns = result
.GetRows()
.Select(r => new Column
{
tableName = r.GetValue<string>("table_name"),
columnName = r.GetValue<string>("column_name"),
type = r.GetValue<string>("type"),
})
.GroupBy(c => c.tableName)
.ToList();
public struct Column
{
public string tableName { get; set; }
public string columnName { get; set; }
public string type { get; set; }
}
To fix it in the driver itself adding this should work:
public override IDictionary Deserialize(ushort protocolVersion, byte[] buffer, int offset, int length, IColumnInfo typeInfo)
{
var mapInfo = (MapColumnInfo) typeInfo;
var keyType = GetClrType(mapInfo.KeyTypeCode, mapInfo.KeyTypeInfo);
var valueType = GetClrType(mapInfo.ValueTypeCode, mapInfo.ValueTypeInfo);
var count = DecodeCollectionLength((ProtocolVersion)protocolVersion, buffer, ref offset);
- var dicType = typeof(SortedDictionary<,>).MakeGenericType(keyType, valueType);
+ IDictionary result;
+
+ if (keyType == typeof(Guid) && valueType == typeof(byte[]))
+ {
+ result = new SortedDictionary<Guid, byte[]>();
+ }
+ else if(keyType == typeof(string) && valueType == typeof(string))
+ {
+ result = new SortedDictionary<string, string>();
+ }
+ else
+ {
+ var dicType = typeof(SortedDictionary<,>).MakeGenericType(keyType, valueType);
+ result = (IDictionary)Activator.CreateInstance(dicType);
+ }
for (var i = 0; i < count; i++)
I understand that the driver relies heavily on reflection, and that full NativeAOT compatibility would likely require significant work. However, adding at least basic NativeAOT support would already be very valuable for applications targeting AOT deployment.
Thanks for considering this.
Hello, I’m using the Cassandra C# driver in applications published with .NET NativeAOT. At the moment, the driver crashes during connection initialization because some generic types are instantiated dynamically through reflection, and the required metadata is not preserved by AOT compilation.
The exception I’m seeing is the following:
From my investigation, the issue happens when the driver dynamically creates instances of
SortedDictionary<TKey, TValue>for types that were not statically referenced anywhere in the application. Since NativeAOT trims unused metadata, those generic instantiations are unavailable at runtime.As a workaround, I was able to make the driver work by explicitly referencing the required concrete generic types in my application:
With this workaround in place, basic driver usage works correctly in NativeAOT mode:
To fix it in the driver itself adding this should work:
public override IDictionary Deserialize(ushort protocolVersion, byte[] buffer, int offset, int length, IColumnInfo typeInfo) { var mapInfo = (MapColumnInfo) typeInfo; var keyType = GetClrType(mapInfo.KeyTypeCode, mapInfo.KeyTypeInfo); var valueType = GetClrType(mapInfo.ValueTypeCode, mapInfo.ValueTypeInfo); var count = DecodeCollectionLength((ProtocolVersion)protocolVersion, buffer, ref offset); - var dicType = typeof(SortedDictionary<,>).MakeGenericType(keyType, valueType); + IDictionary result; + + if (keyType == typeof(Guid) && valueType == typeof(byte[])) + { + result = new SortedDictionary<Guid, byte[]>(); + } + else if(keyType == typeof(string) && valueType == typeof(string)) + { + result = new SortedDictionary<string, string>(); + } + else + { + var dicType = typeof(SortedDictionary<,>).MakeGenericType(keyType, valueType); + result = (IDictionary)Activator.CreateInstance(dicType); + } for (var i = 0; i < count; i++)I understand that the driver relies heavily on reflection, and that full NativeAOT compatibility would likely require significant work. However, adding at least basic NativeAOT support would already be very valuable for applications targeting AOT deployment.
Thanks for considering this.