使用 ServiceStack.Text 将动态对象列表序列化为 CSV
本文关键字:列表 序列化 CSV 对象 动态 ServiceStack Text 使用 | 更新日期: 2023-09-27 18:35:56
我所有的 EF 类都有一个Projection()
方法,可以帮助我选择要从类投影到 SQL 查询的内容:
例:
public static Expression<Func<Something, dynamic>> Projection()
{
return e => new
{
something = e.SomethingId,
name = e.Name,
requiredThingId = e.RequiredThingId,
requiredThing = new
{
requiredThingId = e.RequiredThing.RequiredThingId,
name = e.RequiredThing.Name,
...
},
optionalThingId = e.OptionalThingId,
optionalThing = e.OptionalThingId == null ? null : new
{
optionalThingId = e.OptionalThing.OptionalThingId,
name = e.OptionalThing.Name
}
...
};
}
我使用这些投影方法,如下所示:
List<dynamic> projected = dbContext.Something.Select(Something.Projection()).ToList();
通过这种方式,我可以在我的项目中重用我的投影。
我想使用 ServiceStack.Text 将这些列表序列化为 CSV。
我正在这样做:
string csvString = CsvSerializer.SerializeToCsv<dynamic>(Something.Select(Something.Projection()).ToList());
byte[] csvBytes = System.Text.Encoding.Unicode.GetBytes(csvString);
return File(csvBytes, "text/csv", "foo.csv");
但结果是一个空的csv(csvString充满了"''r'",仅此而已)
问题:
- 我是否正确使用了
SerializeToCsv()
方法? - 我可以在此库中使用
<dynamic>
吗? - 实现我的目的的建议?
对于上面的示例,所需的 CSV 将平展所有属性,如下所示:
"somethingId";"name";"requiredThingId";"requiredThing.requiredThingId";"requiredThing.name";"optionalThingId";"optionalThing.optionalThingId";"optionalThing.name"
回答我自己的问题,但不会标记为已接受,希望有一个新的更大的答案。
我是否正确使用了 SerializeToCsv() 方法?我可以使用动态 在这个图书馆里?
答:两者都是,但需要 ServiceStack.Text v4.0.36(可在 MyGet 上找到,而不是 Nuget)
实现我的目的的建议?
答:使用 ServiceStack.Text
可以将 CSV 序列化为 List<dynamic>
,但所有嵌套属性都将呈现为 JSON,并且它们不会被展平,例如:
List<dynamic> list = new List<dynamic>();
list.Add(new
{
name = "john",
pet = new
{
name = "doggy"
}
});
string csv = CsvSerializer.SerializeToCsv(list);
此列表将序列化为以下 csv:
姓名、宠物
"约翰", { 名字 = "狗狗" }
而不是这个csv,正如我所期望的那样:
姓名、pet_name
"约翰","狗狗"
所以。。。我最终写了这段代码:
public class CsvHelper
{
public static string GetCSVString(List<dynamic> inputList)
{
var outputList = new List<Dictionary<string, object>>();
foreach (var item in inputList)
{
Dictionary<string, object> outputItem = new Dictionary<string, object>();
flatten(item, outputItem, "");
outputList.Add(outputItem);
}
List<string> headers = outputList.SelectMany(t => t.Keys).Distinct().ToList();
string csvString = ";" + string.Join(";", headers.ToArray()) + "'r'n";
foreach (var item in outputList)
{
foreach (string header in headers)
{
if (item.ContainsKey(header) && item[header] != null)
csvString = csvString + ";" + item[header].ToString();
else
csvString = csvString + ";";
}
csvString = csvString + "'r'n";
}
return csvString;
}
private static void flatten(dynamic item, Dictionary<string, object> outputItem, string prefix)
{
if (item == null)
return;
foreach (PropertyInfo propertyInfo in item.GetType().GetProperties())
{
if (!propertyInfo.PropertyType.Name.Contains("AnonymousType"))
outputItem.Add(prefix + "__" + propertyInfo.Name, propertyInfo.GetValue(item));
else
flatten(propertyInfo.GetValue(item), outputItem, (prefix.Equals("") ? propertyInfo.Name : prefix + "__" + propertyInfo.Name));
}
}
}
这样做的是:
它扁平化列表,因此列表中对象的所有属性都是基元(例如:没有嵌套属性)
它从该平展列表创建 CSV。
该算法为 O(n*m),即
n:列表中的
项数m:每个项目内的属性数(包括嵌套属性)
虽然建议使用干净的 POCO 进行序列化,但您可以序列化内联匿名类型,例如:
var somethings = dbContext.Something.Select(e => new {
something = e.SomethingId,
name = e.Name
});
somethings.ToCsv().Print();
否则,我只是在此提交中添加了对序列化IEnumerable<object>
和IEnumerable<dynamic>
的支持。
此更改可从 v4.0.36+ 开始,现在可在 MyGet 上使用。
正在发生的部分原因是库正在使用对象中的属性来确定要序列化的内容。我相信动态对象不会像 POCO 那样构造属性。如果您没有在对象上设置getter
和setter
,它肯定不起作用。仅供参考,我使用此库的版本 4.5.6.0。