Map().Index(0).Constant("abc") throws System.InvalidOperationException - Member is not a property or a field.
Hi, we use the MemberMap.Constant to always write a constant value in a format given to us.
this.Map().Index(0).Constant("const value");
Since the update from v25 to the latest version we get the error:
System.InvalidOperationException
Member is not a property or a field.
at CsvHelper.Expressions.ExpressionManager.CreateGetFieldExpression(MemberMap memberMap)
at CsvHelper.Expressions.ExpressionManager.CreateMemberAssignmentsForMapping(ClassMap mapping, List`1 assignments)
at CsvHelper.Expressions.ObjectRecordCreator.CreateCreateRecordDelegate(Type recordType)
at CsvHelper.Expressions.RecordCreator.GetCreateRecordDelegate(Type recordType)
at CsvHelper.Expressions.RecordCreator.Create[T]()
at CsvHelper.Expressions.RecordManager.Create[T]()
at CsvHelper.CsvReader.GetRecord[T]()
To Reproduce Steps to reproduce the behavior:
- Create a ClassMap<T> with a MemberMap this.Map().Index(0).Constant("const value");
- Create a CsvReader and read the record -> csvReader.GetRecord<T>()
- System.InvalidOperationException is thrown
Expected behavior It reads the record without any error
Additional context For upgrading i used our migration-website but i can't find anything on this topic.
Any recommendations? cheers mMilk
Can you create an example that reproduces this issue? This works for me.
void Main()
{
var s = new StringBuilder();
s.Append("Id,Name\r\n");
s.Append("1\r\n");
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
};
using (var reader = new StringReader(s.ToString()))
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<FooMap>();
csv.GetRecords<Foo>().ToList().Dump();
}
}
private class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
private class FooMap : ClassMap<Foo>
{
public FooMap()
{
Map(m => m.Id).Index(0);
Map(m => m.Name).Index(1).Constant("bar");
}
}
I ran into a similar issue. I think it's caused by not mapping a property to the constant mapping.
In my case, I wanted to just create a constant "Read Date" column for every record, so I thought I could do as @kl1mm did and set an empty mapping to a constant. It would be something like this:
private class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
public class FooMap : ClassMap<Foo>
{
public FooMap()
{
// Print the properties in Foo
Map(m => m.Id).Index(0);
Map(m => m.Name).Index(1).Constant("bar");
// Print a constant that Foo doesn't need, but is useful in exporting
Map().Index(2).Name("Foo Read Date").Constant(DateTime.Now);
}
}
And the output would look something like
| Id | Name | Foo Read Date |
|---|---|---|
| 1 | Foo1 | 4/27/2022 |
| 2 | Foo2 | 4/27/2022 |
For now, my workaround has been to add a dummy property inside of Foo that maps to it because I don't want to have to do any custom "WriteField()" calls for those blanks. Is it possible to do this without the dummy field?
Are there any new findings regarding to the issue? cheers mMilk
Hi, because the package is developing rapidly and this problem still exists, I have found a small workaround hack for our project via reverse engineering.
/// <summary>
/// All the stuff for Workaround: https://github.com/JoshClose/CsvHelper/issues/1967
///
/// this.Map( ).Index(0).Constant("myValue"); not possible so we workaround it with ConstantFixed
/// ^^^ NOTHING here cause we dont need a field/prop here and its allowed from CSVHelper-API
/// </summary>
internal static class MemberMapExtensions
{
public static MemberMap<TClass, TMember> ConstantFixed<TClass, TMember>(this MemberMap<TClass, TMember> mm, TMember constantValue)
{
mm.Constant(constantValue);
// Hacky stuff here
var setter = mm.Data.GetType().GetProperty(nameof(mm.Data.Member))?.GetSetMethod(true);
setter?.Invoke(mm.Data, new object?[] { new MyPropInfo(constantValue) });
return mm;
}
private class MyPropInfo : PropertyInfo
{
private readonly object? constantValue;
public MyPropInfo(object? constantValue) => this.constantValue = constantValue;
public object? Getter() => this.constantValue;
public override object[] GetCustomAttributes(bool inherit) => Array.Empty<object>();
public override object[] GetCustomAttributes(Type attributeType, bool inherit) => Array.Empty<object>();
public override bool IsDefined(Type attributeType, bool inherit) => false;
public override Type? DeclaringType => null;
public override string Name => "dynamic";
public override Type? ReflectedType => null;
public override MethodInfo[] GetAccessors(bool nonPublic) => Array.Empty<MethodInfo>();
public override MethodInfo? GetGetMethod(bool nonPublic) => this.GetType().GetMethod(nameof(this.Getter));
public override ParameterInfo[] GetIndexParameters() => Array.Empty<ParameterInfo>();
public override MethodInfo? GetSetMethod(bool nonPublic) => null;
public override object? GetValue(object? obj, BindingFlags invokeAttr, Binder? binder, object?[]? index, CultureInfo? culture) => null;
public override void SetValue(object? obj, object? value, BindingFlags invokeAttr, Binder? binder, object?[]? index, CultureInfo? culture) { }
public override PropertyAttributes Attributes => PropertyAttributes.None;
public override bool CanRead => true;
public override bool CanWrite => false;
public override Type PropertyType => typeof(string);
}
}
And usage
internal sealed class MyMap: ClassMap<MyClass>
{
public MyMap()
{
this.Map().Index(0).ConstantFixed("<"); // use ConstantFixed insted of Constant
this.Map().Index(1).ConstantFixed(DateTime.Now);
this.Map().Index(2).ConstantFixed(Constants.MyClass.ConstValue);
this.Map(m => m.Id).Index(3);
this.Map(m => m.Name).Index(4);
this.Map().Index(5).ConstantFixed(">");
}
}
For sure its not perfect, but for the moment it helps us. cheers mMilk