无法使用CSVHelper和ClassMap将自定义对象内部的外部对象写入CSV

ecr0jaav  于 2023-07-31  发布在  其他
关注(0)|答案(2)|浏览(107)

我想将Vector3对象(参见https://docs.unity3d.com/ScriptReference/Vector3.html)写入CSV文件,它是我自定义类的一部分:

public class BehaviouralData
{
    public float ActionZ { get; set; }
    public float ActionX { get; set; }
    public int TargetBallAgentHashCode { get; set; }
    public Vector3 TargetBallLocalPosition { get; set; }
    public bool CollectBehaviouralData { get; set; }
    public DateTime ActionTime { get; set; }
    public DateTime Time { get; set; }
}

字符串
因此我使用ClassMap

public sealed class Vector3Map : ClassMap<Vector3>
{
    public Vector3Map()
    {
        Map(m => m.x);
        Map(m => m.y);
        Map(m => m.z);
    }
}


我在写入文件之前注册:

...
using (var csv = new CsvWriter(writer, config))
{
    csv.Context.RegisterClassMap<Vector3Map>();
    csv.WriteRecords(data);
}
...


当我尝试保存文件时,收到以下错误消息:

ArgumentException: Incorrect number of arguments supplied for call to method 'Single get_Item(Int32)'
Parameter name: property
System.Linq.Expressions.Expression.Property (System.Linq.Expressions.Expression expression, System.Reflection.PropertyInfo property) (at <1e18c5a6594041c9844bfd0b6618ee4a>:0)
CsvHelper.Expressions.ExpressionManager.CreateGetMemberExpression (System.Linq.Expressions.Expression recordExpression, CsvHelper.Configuration.ClassMap mapping, CsvHelper.Configuration.MemberMap memberMap) (at <9a1379e089274cc1baf4edcf480e4c6d>:0)
CsvHelper.Expressions.ExpressionManager.CreateGetMemberExpression (System.Linq.Expressions.Expression recordExpression, CsvHelper.Configuration.ClassMap mapping, CsvHelper.Configuration.MemberMap memberMap) (at <9a1379e089274cc1baf4edcf480e4c6d>:0)
CsvHelper.Expressions.ObjectRecordWriter.CreateWriteDelegate[T] (T record) (at <9a1379e089274cc1baf4edcf480e4c6d>:0)
CsvHelper.Expressions.RecordWriter.GetWriteDelegate[T] (T record) (at <9a1379e089274cc1baf4edcf480e4c6d>:0)
CsvHelper.Expressions.RecordWriter.Write[T] (T record) (at <9a1379e089274cc1baf4edcf480e4c6d>:0)
CsvHelper.Expressions.RecordManager.Write[T] (T record) (at <9a1379e089274cc1baf4edcf480e4c6d>:0)
CsvHelper.CsvWriter.WriteRecords[T] (System.Collections.Generic.IEnumerable`1[T] records) (at <9a1379e089274cc1baf4edcf480e4c6d>:0)
Rethrow as WriterException: An unexpected error occurred.
IWriter state:
Row: 2
Index: 0
HeaderRecord:
2
CsvHelper.CsvWriter.WriteRecords[T] (System.Collections.Generic.IEnumerable`1[T] records) (at <9a1379e089274cc1baf4edcf480e4c6d>:0)
Util.SaveDataToCSV[T] (System.String path, System.Collections.Generic.List`1[T] data) (at Assets/Scripts/Util/Util.cs:178)
BehaviourMeasurement.SaveRawBehavioralDataToCSV () (at Assets/Scripts/Measurement/BehaviourMeasurementBehaviour.cs:1021)
BehaviourMeasurementBehaviour.OnDisable () (at Assets/Scripts/Measurement/BehaviourMeasurementBehaviour.cs:122)


空CSV文件包含以下列:

ActionZ,ActionX,TargetBallAgentHashCode,Item,Item,magnitude,sqrMagnitude,magnitude,sqrMagnitude,CollectBehaviouralData,ActionTime,Time


正如你所看到的,并不是坐标被写入文件,而是其他属性(例如magnitude)我不想包括在内。为什么CSVHelper不能将正确的属性Map到CSV文件中?

i86rm4rw

i86rm4rw1#

我在.Net Fiddle里玩了一会儿。
当对BehaviouralData也使用显式Map时

public class BehaviouralDataMap : ClassMap<BehaviouralData>
{
    public BehaviouralDataMap()
    {
        Map(o => o.ActionZ);
        Map(o => o.ActionX);
        Map(o => o.TargetBallAgentHashCode);
        Map(o => o.TargetBallLocalPosition);
        Map(o => o.CollectBehaviouralData);
        Map(o => o.ActionTime);
        Map(o => o.Time);
    }
}

字符串
好像是修好了。
使用自动Map时,如

public class BehaviouralDataMap : ClassMap<BehaviouralData>
{
    public BehaviouralDataMap()
    {
        AutoMap(CultureInfo.InvariantCulture);
    }
}


这可能是默认情况下无论如何都要使用的-它又坏了。
我也试过了,但还是一样。

public class BehaviouralDataMap : ClassMap<BehaviouralData>
{
    public BehaviouralDataMap()
    {
        AutoMap(CultureInfo.InvariantCulture);
        Map(o => o.TargetBallLocalPosition).Ignore();
        Map(o => o.TargetBallLocalPosition.x);
        Map(o => o.TargetBallLocalPosition.y);
        Map(o => o.TargetBallLocalPosition.z);
    }
}

  • 更新 *

实际上这个显式Map也不起作用-_-
它只将Vector3打印到CSV中,因此第二个Map似乎完全被忽略。
很可怕但这个好像有用

public class BehaviouralDataMap : ClassMap<BehaviouralData>
{
    public BehaviouralDataMap()
    {
        Map(o => o.ActionZ);
        Map(o => o.ActionX);
        Map(o => o.TargetBallAgentHashCode);
        Map(o => o.TargetBallLocalPosition.x);
        Map(o => o.TargetBallLocalPosition.y);
        Map(o => o.TargetBallLocalPosition.z);
        Map(o => o.CollectBehaviouralData);
        Map(o => o.ActionTime);
        Map(o => o.Time);
    }
}

说实话,至少对于作家来说,这似乎非常明确,充其量只是一个变通办法。

kmpatx3s

kmpatx3s2#

您的基本问题是,正如derHugoanswer中指出的那样,当CsvHelper自动Map一个类型时,它会自动Map所有属性值的类型,而不是使用以前可能为这些类型注册的任何类Map。特别是在Vector3的情况下,它自动Map其属性,而不是字段xyz,最终导致崩溃。
那么,你的解决方法是什么?除了像derHugo的回答中建议的那样手动Mapxyz字段之外,还有几个额外的选项。

首先,您可以自动Map包含Vector3的类型,然后修复引用。为此,请定义以下泛型ClassMap<T>和关联的扩展方法:

public class CustomAutoMap<TClass> : ClassMap<TClass>
{
    // Automaps the given type then fixes up references to Vector3 (if any).
    public CustomAutoMap()
    {
        AutoMap(CultureInfo.InvariantCulture);
        // Now fix the Vector3 references.
        this.UpdateReferences<Vector3Map, Vector3>();
    }
}

public static partial class CsvExtensions
{
    // Update autoampped reference maps to a different map.
    // Note this should only be done within a ClassMap constructor itself.
    public static ClassMap UpdateReferences<TReferenceMap, TReferenceClass>(this ClassMap classMap, params object[] constructorArgs) where TReferenceMap : ClassMap<TReferenceClass>
    {
        // TODO: nested references of references.
        for (int i = 0, count = classMap.ReferenceMaps.Count; i < count; i++)
            if (classMap.ReferenceMaps[i].Data.Member.MemberType() == typeof(TReferenceClass))
                classMap.UpdateReference<TReferenceMap, TReferenceClass>(i, constructorArgs);
        return classMap;
    }
    
    public static MemberReferenceMap UpdateReference<TReferenceMap, TReferenceClass>(this ClassMap classMap, int index, params object[] constructorArgs) where TReferenceMap : ClassMap<TReferenceClass>
    {
        if (index < 0 || index >= classMap.ReferenceMaps.Count)
            throw new ArgumentException(nameof(index));
        var oldRef = classMap.ReferenceMaps[index];
        classMap.ReferenceMaps.RemoveAt(index);
        var newRef = classMap.References(typeof(TReferenceMap), oldRef.Data.Member, constructorArgs);
        if (oldRef.Data.Prefix != null)
            newRef.Prefix(oldRef.Data.Prefix);
        classMap.ReferenceMaps.Swap(index, classMap.ReferenceMaps.IndexOfWithHint(newRef, classMap.ReferenceMaps.Count - 1));
        return newRef;
    }
    
    static Type? MemberType(this MemberInfo m) =>
        m switch
        {
            PropertyInfo p => p.PropertyType,
            FieldInfo f => f.FieldType,
            _ => null,
        };

    static int IndexOfWithHint<T>(this IList<T> list, T item, int hint) where T : class
    {
        if (hint >= 0 && hint < list.Count && list[hint] == item)
            return hint;
        return list.IndexOf(item);
    }
                                    
    static void Swap<T>(this IList<T> list, int i, int j)
    {
        if (i != j)
        {
            T temp = list[i];
            list[i] = list[j];
            list[j] = temp;
        }
    }
}

字符串
现在序列化BehaviouralData,如下所示:

var config = new CsvConfiguration(CultureInfo.InvariantCulture);
using (var csv = new CsvWriter(writer, config))
{
    csv.Context.RegisterClassMap<CustomAutoMap<BehaviouralData>>();
    csv.WriteRecords(data);
}


其结果是:

ActionZ,ActionX,TargetBallAgentHashCode,CollectBehaviouralData,ActionTime,Time,x,y,z
0,0,1,False,01/01/0001 00:00:00,01/01/0001 00:00:00,1.1,1.1,1.1
0,0,2,False,01/01/0001 00:00:00,01/01/0001 00:00:00,2.1,2.1,2.1
0,0,3,False,01/01/0001 00:00:00,01/01/0001 00:00:00,3.1,3.1,3.1


如果您希望xyz字段有一些前缀,请将[HeaderPrefix("prefix")]添加到TargetBallLocalPosition,如下所示:

public class BehaviouralData
{
    [CsvHelper.Configuration.Attributes.HeaderPrefix("TargetBallLocation_")]
    public Vector3 TargetBallLocalPosition { get; set; }
    // Remainder unchanged


你会得到:

ActionZ,ActionX,TargetBallAgentHashCode,CollectBehaviouralData,ActionTime,Time,TargetBallLocation_x,TargetBallLocation_y,TargetBallLocation_z
0,0,1,False,01/01/0001 00:00:00,01/01/0001 00:00:00,1.1,1.1,1.1
0,0,2,False,01/01/0001 00:00:00,01/01/0001 00:00:00,2.1,2.1,2.1
0,0,3,False,01/01/0001 00:00:00,01/01/0001 00:00:00,3.1,3.1,3.1


如果您需要对BehaviouralData的格式进行更多的手动控制,则需要创建一个手动类Map。当你这样做时,使用方法ClassMap<TClass>.References<TClassMap>(Expression<Func<TClass, object>> expression, params object[])而不是ClassMap<TClass>.Map()来引用Vector3的内容,使用你的Vector3Map

public class BehaviouralDataMapExplicit : ClassMap<BehaviouralData>
{
    public BehaviouralDataMapExplicit()
    {
        Map(m => m.ActionZ);
        Map(m => m.ActionX);
        Map(m => m.TargetBallAgentHashCode);
        References<Vector3Map>(m => m.TargetBallLocalPosition)
            .Prefix("TargetBallLocation_");
        Map(m => m.CollectBehaviouralData);
        Map(m => m.ActionTime);
        Map(m => m.Time);
    }
}


演示小提琴here
[1]CsvHelper似乎有一个bug。具体来说,对于没有实现IEnumerable的类,它会尝试自动Map**indexed properties**,导致您看到的异常。这是CsvHelper中的一个简单的错误,它应该在自动Map反射发现的任何属性之前检查PropertyInfo.GetIndexParameters().Length == 0。但是即使CsvHelper修复了这个bug,您仍然无法得到您想要的东西,也就是Vector3的三个字段,而不是它的属性。

相关问题