【问题标题】:Can I 'flatten' an XDocument with Linq?我可以用 Linq '展平'一个 XDocument 吗?
【发布时间】:2012-01-18 03:52:27
【问题描述】:

我有一个高度嵌套的 XML 文档,我需要将其加载到我的数据库中以进行额外处理。由于超出本讨论范围的各种原因,我需要将该结构“展平”,然后将其加载到 DataTables 中,然后我可以将其 SQLBulkCopy 复制到将被处理的数据库中。所以假设我的原始 XML 看起来像这样(我的嵌套更深,但这是基本思想):

<data>
    <report id="1234" name="XYZ">
        <department id="234" name="Accounting">
            <item id="ABCD" name="some item">
                <detail id="detail1" value="1"/>                    
                <detail id="detail2" value="2"/>                    
                <detail id="detail3" value="3"/>                    
            </item>
        </department>      
    </report>
 </data>   

我想将其扁平化为单个(尽管是冗余的)表结构,其中每个属性都变成一列(即 ReportId、ReportName、DepartmentId、DepartmentName、ItemId、ItemName、Detail1、Detail2、Detail3)。

所以我的问题只是“是否可以通过简单的 Linq 查询来完成此任务”?过去我只会写一些 XSLT 并完成它,但我很好奇 Linq 库是否可以完成同样的事情?

谢谢!

【问题讨论】:

  • 如果你能展示你的样本输入在转换后的样子,那真的很有帮助。看你的描述还不清楚。

标签: linq linq-to-xml


【解决方案1】:

这是你要找的吗?

var doc = XDocument.Load(fileName);
var details =
    from report in doc.Root.Elements("report")
    from department in report.Elements("department")
    from item in department.Elements("item")
    from detail in item.Elements("detail")
    select new
    {
        ReportId = (int)report.Attribute("id"),
        ReportName = (string)report.Attribute("name"),
        DepartmentId = (int)department.Attribute("id"),
        DepartmentName = (string)department.Attribute("name"),
        ItemId = (string)item.Attribute("id"),
        ItemName = (string)item.Attribute("name"),
        DetailId = (string)detail.Attribute("id"),
        DetailValue = (int)detail.Attribute("value"),
    };

如果你想要它作为DataTable,你可以使用以下扩展方法:

public static DataTable ToDataTable<T>(this IEnumerable<T> source)
{
    PropertyInfo[] properties = typeof(T).GetProperties()
                                         .Where(p => p.CanRead && !p.GetIndexParameters().Any())
                                         .ToArray();

    DataTable table = new DataTable();
    foreach (var p in properties)
    {
        Type type = p.PropertyType;
        bool allowNull = !type.IsValueType;
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            allowNull = true;
            type = Nullable.GetUnderlyingType(type);
        }
        DataColumn column = table.Columns.Add(p.Name, type);
        column.AllowDBNull = allowNull;
        column.ReadOnly = !p.CanWrite;
    }

    foreach (var item in source)
    {
        DataRow row = table.NewRow();
        foreach (var p in properties)
        {
            object value = p.GetValue(item, null) ?? DBNull.Value;
            row[p.Name] = value;
        }
        table.Rows.Add(row);
    }

    return table;
}

像这样使用它:

var table = details.CopyToDataTable();

【讨论】:

  • +1 正要发布类似的内容,尽管您的回答使用的中间步骤比我的方法少。
  • 如此简单和优雅,我为自己没有想到它而打自己一巴掌,谢谢你的帮助。您发布的代码需要做一个小改动......因为有一对多 节点,我需要根据其属性“id”字段映射每个节点(例如 需要映射到名为 Detail1 的列。我使用这样的另一个查询使其工作:(来自 a in detail.Elements("detail") where (string)a.Attribute("id") == "detail1" select a.Attribute("value").Value).Single() 但可能有更简单的方法
猜你喜欢
  • 1970-01-01
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
  • 2010-11-29
  • 1970-01-01
  • 1970-01-01
  • 2017-03-24
  • 2016-11-21
相关资源
最近更新 更多