LINQPad:如何向转储的每一行添加 属性?

LINQPad: How do I add a property to each row being dumped?

在 LINQPad 中,我经常想转储一些 SQL table,但在每一行添加一个 属性。我通常默认为简单的:

Publishers
.Select(p => new { p, AppCount = p.Apps.Count() })

但这当然会导致输出混乱,因为每个项目都被格式化为垂直 table,并且有一个额外的列包含添加的 属性。我可以创建一个新对象,但这非常冗长,而且您会丢失 table 遍历链接:

Publishers
.Select(p => new { p.PublisherId, p.Name, p.Added, ..., AppCount = p.Apps.Count() })

我喜欢的是某种合并语法,它创建一个动态对象来转储添加的 属性:

Publishers
.Select(p => p.Merge(new { AppCount = p.Apps.Count() })

我尝试了几种不同的方法来实现这一点:使用 JsonConvert.SerializeObject(试图序列化所有外键引用和重复循环,使用 ExpandoObject(无法想出一个很好的干净的语法),以及像 https://github.com/kfinley/TypeMerger 这样的库,我也没有开始工作。

有没有人想出一个干净的方法来做到这一点? LINQPad 是否有一些我可以使用的内置 synax?谢谢!

更新:我上传了一个示例脚本来演示这个问题,让任何人尝试他们的解决方案:http://share.linqpad.net/kclxpl.linq

到 运行,您需要填充 Nutshell 数据库,您可以使用 LINQPad 中的“填充演示数据库”示例来完成此操作。

您可以添加一些扩展方法来完成这项工作。我不建议将这些添加到 My Extensions,因为它们需要导入 System.Dynamic,而对于大多数查询,您可能不需要。将它们放在另一个查询中,在需要时导入 System.Dynamic#load

public static class ExpandoObjectExt {
    // convert object to ExpandoObject, add a property and return as dynamic
    public static dynamic With<T, TAdd>(this T obj, string propName, TAdd val) {
        var eo = Util.ToExpando(obj);
        var objDict = (IDictionary<string, object>)eo;
        objDict[propName] = val;
        return eo;
    }
    // convert IEnumerable<T> to IEnumerable<dynamic>, adding a property to each object
    public static IEnumerable<dynamic> With<T, TAdd>(this IEnumerable<T> src, string propName, Func<T, TAdd> valFn) => src.Select(s => s.With(propName, valFn(s)));
}

现在你可以这样使用了:

Publishers
    .With("AppCount", p => p.Apps.Count())

但是,转换为 ExpandoObject 将消除集合属性的任何超链接,您可以通过使用显式 .Dump(1) 仅指定一级输出来解决此问题。您还可以编写自定义 Util.ToExpando(代码为 here),使用 Util.OnDemand 为集合属性创建超链接。

此外,如果您想添加多个 属性,您可以编写 With 的变体,它采用多对或测试 ExpandoObject 的版本(和 IEnumerable<ExpandoObject>) 并且可以链接流畅的风格。

我对 NetMage 的扩展方法略有不同。试试这个:

public static object Extendo(this object @object, Action<dynamic> inject)
{
    dynamic e = Util.ToExpando(@object);
    inject(e);
    return (object)e;
}

Action<dynamic>允许这样写代码:

var people = new[]
{
    new { name = "Fred", age = 41 },
    new { name = "John", age = 67 },
    new { name = "Dave", age = 23 },
};

people
    .Select(p => p.Extendo(d => d.IsOld = p.age >= 55))
    .Dump();

我可以用这个方法稍微扩展一下:

public static object Extendo<T>(this IEnumerable<T> source, Action<T, dynamic> inject) =>
    source.Select(x => x.Extendo(d => inject(x, d)));

现在我可以这样写了:

people.Extendo((p, d) => d.IsOld = p.age >= 55).Dump();

我得到了相同的结果。

当然,这些方法有效地 return object 因此无法进行进一步的计算。因此,直奔 Dump 可能是值得的。试试这个:

public static void Dump<T>(this IEnumerable<T> source, Action<T, dynamic> inject) =>
    source.Select(x => x.Extendo(d => inject(x, d))).Dump();

现在你只要这样写:

people.Dump((p, d) => d.IsOld = p.age >= 55);

这是一个很难解决的问题。部分问题在于,当将延迟加载的属性从其“实体”主页中取出并移至 ExpandoObject 时,允许 LINQPad 识别延迟加载属性的元数据会丢失,从而导致过多 round-tripping.

为了避免麻烦,我在 LINQPad 7.2.7 中添加了一个 Util.Merge 方法。这是它最简单的用法:

var o1 = new { A = 1, B = 2 };
var o2 = new { C = 3, D = 4 };

Util.Merge (o1, o2).Dump();

对于手头的问题:

Publishers
   .Select(p => Util.Merge (p, new { AppCount = p.Apps.Count() }))