【问题标题】:Programming a custom union in C# (join between many objects)在 C# 中编写自定义联合(连接多个对象)
【发布时间】:2010-01-06 15:27:31
【问题描述】:

场景

我需要从给定规范的对象列表中构建数据表。例如,我有一个 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 中添加一个新行。

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

谢谢, 斯蒂芬

【问题讨论】:

  • 我假设您想构建这些 DataTable 对象之一以放在“DataGridView”后面(已针对与 DataTable 一起使用进行了优化)。

标签: c# reflection join union


【解决方案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.
}

【讨论】:

  • 如果这就是您的 Linq 查询,它可能确实是一个扁平化的投影。这个查询看起来很像我想要做的,除了它不是通用的。恐怕我对 Linq 没有太多经验。
【解决方案2】:

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

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
}

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

【讨论】:

  • 据我所知,这段代码只为每个“主人”创建了一行。这个项目的简单部分是遍历每个主对象和细节对象,困难的部分(在我看来)是创建主/细节/细节......行(如果有意义的话)。
  • 它没有。我猜到你想要什么,我想我至少接近了,尽管 union 让我有点失望,因为它是 SQL 中最常用的术语,现在我更困惑了。尝试添加您想要的 UI 的图片。
  • 嗯,没有用于结果的 UI,而是用于创建规范的 UI。我将编辑我的帖子并自己添加一些伪代码,说明我认为算法必须如何工作。
猜你喜欢
  • 1970-01-01
  • 2014-05-28
  • 1970-01-01
  • 2016-04-17
  • 2023-03-13
  • 1970-01-01
  • 2017-01-26
  • 1970-01-01
  • 2020-11-21
相关资源
最近更新 更多