querybuilder icon indicating copy to clipboard operation
querybuilder copied to clipboard

KeyAttribute malfunctioning when used along with CsvHelper

Open kwon0408 opened this issue 9 months ago • 1 comments

I have used SqlKata's [Key] attribute without the name specified for years without any errors complaining about it, even with SqlKata.Execution.

However CsvHelper seems to be not happy with these nameless keys.

using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;

MyRecord[] records =
[
    new() { Id = 1, Value = "A" },
    new() { Id = 2, Value = "B" },
];

var csv = WriteCsv(records);
Console.WriteLine(csv);

static string WriteCsv<T>(IEnumerable<T> values)
{
    using var writer = new StringWriter();
    var config = new CsvConfiguration(CultureInfo.InvariantCulture)
    {
        MemberTypes = MemberTypes.Properties | MemberTypes.Fields
    };
    using var csv = new CsvWriter(writer, config);

    csv.WriteRecords(values);
    return writer.ToString();
}

struct MyRecord
{
    [SqlKata.Key] // Error! 
    // [SqlKata.Key(nameof(Id))] or any other value for `name` is REQUIRED, when using with CsvHelper
    public int Id;

    public string Value;
}

The problem occurs when [Key] is used without Name (which defaults to ""). If the code above is executed, an exception is thrown like:

Unhandled exception. CsvHelper.WriterException: An unexpected error occurred. See inner exception for details.
IWriter state:
   Row: 1
   Index: 0
   HeaderRecord:
1

 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'name')
   at SqlKata.ColumnAttribute..ctor(String name)
   at SqlKata.KeyAttribute..ctor(String name)
   at System.Reflection.CustomAttribute._CreateCaObject(RuntimeModule pModule, RuntimeType type, IRuntimeMethodInfo pCtor, Byte** ppBlob, Byte* pEndBlob, Int32* pcNamedArgs)
   at System.Reflection.CustomAttribute.AddCustomAttributes(ListBuilder`1& attributes, RuntimeModule decoratedModule, Int32 decoratedMetadataToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, ListBuilder`1 derivedAttributes)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeFieldInfo field, RuntimeType caType)
   at System.Attribute.GetCustomAttributes(MemberInfo element, Boolean inherit)
   at CsvHelper.Configuration.ClassMap.ApplyAttributes(MemberMap memberMap)
   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, CsvContext context, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMap(CsvContext context)
   at CsvHelper.CsvContext.AutoMap(Type type)
   at CsvHelper.CsvWriter.WriteHeader(Type type)
   at CsvHelper.CsvWriter.WriteHeaderFromType[T]()
   at CsvHelper.CsvWriter.WriteRecords[T](IEnumerable`1 records)
   --- End of inner exception stack trace ---
   at CsvHelper.CsvWriter.WriteRecords[T](IEnumerable`1 records)
   at Program.<<Main>$>g__WriteCsv|0_0[T](IEnumerable`1 values) in C:\Users\user\source\repos\sln1\proj1\Program.cs:line 26
   at Program.<Main>$(String[] args) in C:\Users\user\source\repos\sln1\proj1\Program.cs:line 13

kwon0408 avatar Apr 16 '25 05:04 kwon0408

The problem occurs when [Key] is used without Name (which defaults to "")

Yes but no. Pay attention to CallerMemberName attribute on name parameter; https://github.com/sqlkata/querybuilder/blob/d20e930c8a4bd801225f96302917432fa4b3b3ea/QueryBuilder/ColumnAttribute.cs#L27

CallerMemberName attribute doesn't work with fields, So either convert your struct something like this;

struct MyRecord
{
    [SqlKata.Key]
    public int Id { get; set; }

    public string Value;
}

Or just pass nameof(Id) as parameter.

cemahseri avatar Apr 26 '25 14:04 cemahseri