2

设想

我需要从给定规范的对象列表中构建数据表。例如,我有一个 Order 对象的列表/数组,以及一个详细说明数据表应包含的内容的字符串规范,例如“ID;Date;Customer.ID;Customer.Name;Orderlines.Product.ID;Orderlines.Quantity;Orderlines。单价”。

order 类包含 Orderlines 的列表(详细信息),Orderline 类包含对 Product 的引用等。无论如何都是一个非常面向对象的设计。

我需要创建一个通用过程,它采用对象列表和字符串规范,然后找到所有连接。例如 AddToDataTableWithJoins(DataTable table, object[] objects, string specification)。

如果数组中存在两个订单,每个订单都有三个订单行,则结果将是一个有 6 行的数据表。

例如

{1,'2009-12-12 00:00',14,'John','DRY14',12.00,19.99}
{1,'2009-12-12 00:00',14,'John','DRY15',9.00,12.00}
{1,'2009-12-12 00:00',14,'John','DRY16',3,3.00}
{2,'2009-12-13 00:00',17,'Mike','ALR',10.00,16.00}
{2,'2009-12-13 00:00',17,'Mike','BBR',1.00,11.50}
{2,'2009-12-13 00:00',17,'Mike','PPQ',4,6.00}

但是话又说回来,Order 类可能有多个列表(详细信息),我必须承认,即使我熟悉反射和简单递归,我对此一无所知。

非常感谢任何有关如何创建此算法的建议。

想法

必须实施一个限制,以便规范的每一级中不存在多个列表,并且不同分支中不存在列表。例如,如果 Customer 类定义了一个 Order 对象列表,则不能允许以下规范:"ID;Date;Customer.ID;Customer.Orders.ID;Orderlines.Product.ID"

那么我相信,必须使用以下方法:

  1. 确定包含一个或多个一对多关系的分支。
  2. 遍历集合中的每个根对象(Order 对象)。
  3. 对于根对象中的每个属性,将不涉及一对多关系的每个属性的值存储在一个数组中。
  4. 使用递归并遍历复制数组的子集合中的每个对象。
  5. 当到达最外面的“节点”时,在 DataTable 中添加一个新行。

这些点可能会被修改,因为它们只是在这一点上的想法,但我想我已经接近了一些东西。

谢谢,斯特凡

4

2 回答 2

1

在我看来,这听起来更像是一个扁平化的投影,而不是联合或连接。如果是这种情况,您应该能够执行以下操作:

var q = from o in orders
        from ol in o.OrderLines
        select new { o.Id, o.Date, o.Customer.Name, ol.Product.Id, ol.Quantity }

(我在投影中遗漏了一些属性,但您应该了解总体思路)

这将为您提供一个匿名类型的 IEnumerable,您现在可以遍历它以打印出数据(或任何您想做的事情):

foreach(var item in q)
{
    Console.Write(item.Id);
    Console.Write(item.Date);
    // etc.
}
于 2010-01-06T15:44:44.847 回答
0

粗略的大纲,这是伪代码:

void AddToDataTableWithJoins(DataTable table, object[] objects,
  string specification)
{
  // 1. Split specification into parts on semicolon separator
  string[] specificationParts = ...

  // 2. Split parts into name lists (split on dot)
  string[][] specificationPartsNameLists = ...

  // 3. Set up columns (use first object's field types as example)
  for (int c=0; c<specificationParts.length; c++) {
    string mungedSpecPart = // might replace "." with something, does "_" work?
    table.Columns.Add(mungedSpecPart,
      getTypeForPath(specificationPartsNameLists[c],
      objects[0]));
  }

  // 4. Set up row values container
  object[] rowItems = new object[specificationParts.length];

  for (int d=0; d < objects.length; d++) {
    object obj = objects[d];
    for (int c=0; c < specificationParts.length; c++) {
      // 5. Add row values
      rowItems[c] = getValueForPath(specificationPartsNameLists[c], obj);
    }
    // 6. Invoke row add
    SomeInvokerFramework.invoke(table.Rows, "Add", rowItems);
  }
  // 7. Return
}

object getTypeForPath(string[] path, object inObject) {
  // do reflection-ey stuff to retrieve named data path and return type
}

object getValueForPath(string[] path, object object) {
  // do reflection-ey stuff to retrieve named data path and return value
}

如果以后对象的字段类型不匹配或字段不存在(!)或对象为空,您可能还需要添加错误检查/处理。并且您可能希望在遍历行时添加类型检查断言。

代码可以搜索所有对象,直到找到列的非 NULL 字段,以推断列类型(如果您想开始支持 NULL)。请记住,如果在所有行中都为 NULL,则无法为字段设置类型,因为例程无法从中推断类型。如果您需要支持 NULL,您可能需要提供一个类型数组,或者默认使用一个全 NULL 列来键入字符串或其他内容。

编辑:重新格式化的源代码。将 typeof 调用更改为对 getTypeForPath() 的调用。

编辑:您添加了执行类似 SQL 连接的操作的要求,基本上,其中数据路径包含一对多连接,以针对一对多关系重复数组中每个子对象的行。大概如果有几个你想首先按最左边的一对多关系排序,然后是最左边的第二个等等。

像这样的东西,我建议。正如我之前所说,这只是伪代码,我真的试图说明函数的形状和方法,因为它是一个相当困难的问题,而不是为你写它。以下代码可能包含错误,并且可能有一些错误:

void AddToDataTableWithJoins(DataTable table, object[] objects,
  string specification)
{
  // 1. Split specification into parts on semicolon separator
  string[] specificationParts = ...

  // 2. Split parts into name lists (split on dot)
  string[][] specificationPartsNameLists = ...

  // 2a. Set up data for whether field is simple or to be iterated
  boolean[][] specPartIsToBeIterated = ...

  // 3. Set up columns (use first object's field types as example)
  for (int c=0; c<specificationParts.length; c++) {
    string mungedSpecPart = // might replace "." with something, does "_" work?
    table.Columns.Add(mungedSpecPart,
      getTypeForPath(specificationPartsNameLists[c],
      objects));
    // 3a. set up should iterate flags
    for (int d=1; d < specificationPartsNameLists[c].length; d++) {
      string[] temp = new string[e];
      for (int e=0; e < d; e++) temp[e] = specificationPartsNameLists[c][e];
      specPartIsToBeIterated[c][d] = isDataPathOneToMany(temp, objects);
    }
  }

  // 4. Set up row values container
  object[] rowItems = new object[specificationParts.length];

  // 4a. Set up index positions container for one-to-many subelement iterations
  int[] rowIndices = new int[specificationParts.length];

  for (int d=0; d < objects.length; d++) {
    // 4b. Set up one-to-many position counters
    for (int e=0; e < rowIndices.length; e++) rowIndices[e] = 0;

    // 4c. Start subscript iterator loop
    for (;;) {

      object obj = objects[d];
      for (int c=0; c < specificationParts.length; c++) {
        // 5. Add row values
        rowItems[c] = getValueForPath(specificationPartsNameLists[c],
          rowIndices, obj);
      }
      // 6. Invoke row add
      SomeInvokerFramework.invoke(table.Rows, "Add", rowItems);

      // 6a. Work out whether we need to iterate more rows
      for (int e=rowIndices.length-1; e>=0; e--) {
        boolean domore=false;
        if (specPartIsToBeIterated[e]) {
          string[] pathToGetIndex = // calc string[] to get count of objects
          int count = getCountForPath(pathToGetIndex, rowIndices, obj);
          if (rowIndices[e]<(count-1)) {
            rowIndices[e]++; domore=true; break;
            for (e++; e<rowIndices.length; e++) {
              if (specPartIsToBeIterated[e]) rowIndices[e]=0;
            }
          }
        }
      }
      // 6b. Break to next object if we're done on this one
      if (!domore) break;
    }
  }
  // 7. Return
}

object getTypeForPath(string[] path, object[] inObjects) {
  // do reflection-ey stuff to retrieve named data path and return type
}

boolean isDataPathOneToMany(string[] path, object[] inObjects) {
  // do reflection-ey stuff to retrieve named data path and return type
}

object getValueForPath(string[] path, int[] rowIndices, object object) {
  // do reflection-ey stuff to retrieve named data path and return value
  // where there are one-to-many relationships corresponding item in rowIndices
  // array identifies which subelement in the array
  // etc
}

object getCountForPath(string[] path, int[] rowIndices, object object) {
  // do reflection-ey stuff to retrieve named data path and return count
  // where there are one-to-many relationships corresponding item in rowIndices
  // array identifies which subelement in the array.  for convenience function
  // accepts an over-long rowIndices array
}

编辑:添加“并且可能有一些错误”:-)

于 2010-01-06T15:59:21.590 回答