3

我有一个非常奇怪的问题,不知道我应该采取哪种方式来解决它。

我有一个IEnumerable<Dictionary<string,object>>,它可以包含一个或多个IEnumerable<Dictionary<string,object>>.

现在,需要将此 Dictionary 导入 DataTable,如果IEnumberable<Dictionary<string,object>>里面有 0 个子项,则 DataTable 应该只包含一行,其中 Column 名称为字符串,RowData 为对象(本例中为字符串)。但是,如果有一个孩子,那么 DataTable 应该包含与这个孩子相同的行数,以及来自父级的每一行中的其他信息。

例如,父 Dictionary 具有以下值:

字符串,对象
---------------
名字,迈克
姓氏,泰森

IEnumerable 字典子级具有:

字符串,对象
----------------
[0]
孩子的名字,约翰
儿童年龄, 10

[1]
孩子的名字,托尼
儿童年龄, 12

结果应该是:

姓名 LastName ChildName ChildAge
------------------------------------------
迈克泰森约翰 10
迈克泰森托尼 12

此外,父 IEnumerable 可以有许多子 IEnumerable,但它们的大小都相同。

有谁知道如何解决这个问题?

static void Main(string[] args)
{
    var child1 = new List<Dictionary<string, object>>();
    var childOneDic = new Dictionary<string, object> 
    { 
        { "ChildName", "John" }, 
        { "ChildAge", 10 } 
    };
    child1.Add(childOneDic);

    var child2 = new List<Dictionary<string, object>>();
    var childTwoDic = new Dictionary<string, object> 
    { 
        { "ChildName", "Tony" }, 
        { "ChildAge", 12 } 
    };
    child2.Add(childTwoDic);

    var parrent = new List<Dictionary<string, object>>();
    var parrentDic = new Dictionary<string, object>
    {
        { "Name", "Mike" },
        { "LastName", "Tyson" },
        { "child1", child1 },
        { "child2", child2 }
    };
    parrent.Add(parrentDic);

    var table = new DataTable();
    table.Columns.Add("Name");
    table.Columns.Add("LastName");
    table.Columns.Add("ChildName");
    table.Columns.Add("ChildAge");
    table = CreateTable(parrent, null, table);
}

static DataTable CreateTable(IEnumerable<Dictionary<string, object>> parrent, 
                             DataRow row, DataTable table)
{
    if (row == null)
    {
        row = table.NewRow();
    }

    foreach (var v in parrent)
    {
        foreach (var o in v)
        {
            if (o.Value.GetType().IsGenericType)
            {
                var dic = (IEnumerable<Dictionary<string, object>>) o.Value;
                CreateTable(dic, row, table);
            }
            else
            {
                row[o.Key] = o.Value;
            }
        }
        if (row.RowState == DataRowState.Added)
        {
            DataRow tempRow = table.NewRow();
            tempRow.ItemArray = row.ItemArray;
            table.Rows.Add(tempRow);
        }
        else
        {
            table.Rows.Add(row);
        }
    }

    return table;
}
4

2 回答 2

4

Linq 是这项工作的理想人选。我仍然认为你应该重新考虑设计,这是一件非常可怕的事情。这应该这样做(并且没有任何硬编码):

var child1 = new List<IDictionary<string, object>>
{
    new Dictionary<string, object> { { "ChildName", "John" }, { "ChildAge", 10 } }
};

var child2 = new List<IDictionary<string, object>>
{
    new Dictionary<string, object> { { "ChildName", "Tony" }, { "ChildAge", 12 } }
};

var parent = new List<IDictionary<string, object>>
{
    new Dictionary<string, object> 
    { 
        { "Name", "Mike" },
        { "LastName", "Tyson" },
        { "child1", child1 },
        { "child2", child2 }
    },
    new Dictionary<string, object>
    {
        { "Name", "Lykke" },
        { "LastName", "Li" },
        { "child1", child1 },
    },
    new Dictionary<string, object>
    { 
        { "Name", "Mike" },
        { "LastName", "Oldfield" }
    }
};

CreateTable(parent);

static DataTable CreateTable(IEnumerable<IDictionary<string, object>> parents)
{
    var table = new DataTable();

    foreach (var parent in parents)
    {
        var children = parent.Values
                             .OfType<IEnumerable<IDictionary<string, object>>>()
                             .ToArray();

        var length = children.Any() ? children.Length : 1;

        var parentEntries = parent.Where(x => x.Value is string)
                                  .Repeat(length)
                                  .ToLookup(x => x.Key, x => x.Value);
        var childEntries = children.SelectMany(x => x.First())
                                   .ToLookup(x => x.Key, x => x.Value);

        var allEntries = parentEntries.Concat(childEntries)
                                      .ToDictionary(x => x.Key, x => x.ToArray());

        var headers = allEntries.Select(x => x.Key)
                                .Except(table.Columns
                                             .Cast<DataColumn>()
                                             .Select(x => x.ColumnName))
                                .Select(x => new DataColumn(x))
                                .ToArray();
        table.Columns.AddRange(headers);

        var addedRows = new int[length];
        for (int i = 0; i < length; i++)
            addedRows[i] = table.Rows.IndexOf(table.Rows.Add());

        foreach (DataColumn col in table.Columns)
        {
            object[] columnRows;
            if (!allEntries.TryGetValue(col.ColumnName, out columnRows))
                continue;

            for (int i = 0; i < addedRows.Length; i++)
                table.Rows[addedRows[i]][col] = columnRows[i];
        }
    }

    return table;
}

这是我使用过的一种扩展方法:

public static IEnumerable<T> Repeat<T>(this IEnumerable<T> source, int times)
{
    source = source.ToArray();
    return Enumerable.Range(0, times).SelectMany(_ => source);
}

您可以addedRows以更惯用的方式(我更喜欢)创建变量,但对其他人来说可能不太可读。在一行中,如下所示:

var addedRows = Enumerable.Range(0, length)
                          .Select(x => new
                          {
                              relativeIndex = x,
                              actualIndex = table.Rows.IndexOf(table.Rows.Add())
                          })
                          .ToArray();

这里棘手的部分是正确旋转。在我们的例子中没什么大不了的,因为我们可以使用索引器。用一组示例进行测试,如果这有问题,请告诉我..


另一种方法是预先计算标题(循环之前的数据表列),因为它无论如何都不会改变。但这也意味着额外的一轮枚举。至于哪个更有效,您必须对其进行测试。我发现第一个更优雅。

static DataTable CreateTable(IEnumerable<IDictionary<string, object>> parents)
{
    var table = new DataTable();

    //excuse the meaningless variable names

    var c = parents.FirstOrDefault(x => x.Values
                                         .OfType<IEnumerable<IDictionary<string, object>>>()
                                         .Any());
    var p = c ?? parents.FirstOrDefault();
    if (p == null)
        return table;

    var headers = p.Where(x => x.Value is string)
                   .Select(x => x.Key)
                   .Concat(c == null ? 
                           Enumerable.Empty<string>() : 
                           c.Values
                            .OfType<IEnumerable<IDictionary<string, object>>>()
                            .First()
                            .SelectMany(x => x.Keys))
                   .Select(x => new DataColumn(x))
                   .ToArray();
    table.Columns.AddRange(headers);

    foreach (var parent in parents)
    {
        var children = parent.Values
                             .OfType<IEnumerable<IDictionary<string, object>>>()
                             .ToArray();

        var length = children.Any() ? children.Length : 1;

        var parentEntries = parent.Where(x => x.Value is string)
                                  .Repeat(length)
                                  .ToLookup(x => x.Key, x => x.Value);
        var childEntries = children.SelectMany(x => x.First())
                                   .ToLookup(x => x.Key, x => x.Value);

        var allEntries = parentEntries.Concat(childEntries)
                                      .ToDictionary(x => x.Key, x => x.ToArray());

        var addedRows = Enumerable.Range(0, length)
                                  .Select(x => new
                                  {
                                      relativeIndex = x,
                                      actualIndex = table.Rows.IndexOf(table.Rows.Add())
                                  })
                                  .ToArray();

        foreach (DataColumn col in table.Columns)
        {
            object[] columnRows;
            if (!allEntries.TryGetValue(col.ColumnName, out columnRows))
                continue;

            foreach (var row in addedRows)
                table.Rows[row.actualIndex][col] = columnRows[row.relativeIndex];
        }
    }

    return table;
}
于 2013-09-20T22:26:28.423 回答
0

我建议您使用类,这样可以更轻松地查看和操作数据。这是根据您提供的示例可以执行的操作的示例。诀窍还在于将父级的引用保留在子级内部。您只需要将子列表传递给网格。

    static void Main()
    {
        var child1 = new List<Dictionary<string, object>>();
        var childOneDic = new Dictionary<string, object> 
{ 
    { "ChildName", "John" }, 
    { "ChildAge", 10 } 
};
        child1.Add(childOneDic);

        var child2 = new List<Dictionary<string, object>>();
        var childTwoDic = new Dictionary<string, object> 
{ 
    { "ChildName", "Tony" }, 
    { "ChildAge", 12 } 
};
        child2.Add(childTwoDic);

        var parrent = new List<Dictionary<string, object>>();
        var parrentDic = new Dictionary<string, object>
{
    { "Name", "Mike" },
    { "LastName", "Tyson" },
    { "child1", child1 },
    { "child2", child2 }
};
        parrent.Add(parrentDic);


        List<Parent> goodList = new List<Parent>();
        List<Child> allChilds = new List<Child>();

        foreach (Dictionary<string, object> p in parrent)
        {
            Parent newParent = new Parent(p);

            goodList.Add(newParent);
            allChilds.AddRange(newParent.Childs);
        }

        foreach (Child c in allChilds)
        {
            Console.WriteLine(c.ParentName + ":" + c.ParentName + ":" + c.Name + ":" + c.Age);
        }

        Console.ReadLine();

    }

    public class Parent
    {
        private List<Child> _childs = new List<Child>();

        private Dictionary<string, object> _dto;

        public Parent(Dictionary<string, object> dto)
        {
            _dto = dto;

            for (int i = 0; i <= 99; i++)
            {
                if (_dto.ContainsKey("child" + i))
                {
                    _childs.Add(new Child(((List<Dictionary<string, object>>)_dto["child" + i])[0], this));
                }
            }

        }

        public string Name
        {
            get { return (string)_dto["Name"]; }
        }

        public string LastName
        {
            get { return (string)_dto["LastName"]; }
        }

        public List<Child> Childs
        {
            get { return _childs; }

        }

    }

    public class Child
    {

        private Parent _parent;

        private Dictionary<string, object> _dto;

        public Child(Dictionary<string, object> dto, Parent parent)
        {
            _parent = parent;
            _dto = dto;

        }

        public string Name
        {
            get { return (string)_dto["ChildName"]; }
        }


        public int Age
        {
            get { return (int)_dto["ChildAge"]; }
        }

        public string ParentName
        {
            get { return _parent.Name; }
        }

        public string ParentLastName
        {
            get { return _parent.LastName; }
        }

    }
}
于 2013-09-20T19:44:07.313 回答