用 C# 编写自定义联合(多个对象之间的联接)

发布于 2024-08-17 03:47:58 字数 1258 浏览 4 评论 0原文

场景

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

订单类包含订单行的列表(详细信息),订单行类包含对产品的引用等。无论如何,这是一个非常面向对象的设计。

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

如果数组中存在两个订单,每个订单具有三个订单行,则结果将是一个包含 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. 当到达最外面的“节点”时,在数据表中添加一个新行。

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

谢谢, 斯特凡

Scenario

I need to build a table of data from a list of objects given a specification. In example, I have a list/array of Order objects, and a string specification detailing what the data table should contain e.g "ID;Date;Customer.ID;Customer.Name;Orderlines.Product.ID;Orderlines.Quantity;Orderlines.UnitPrice".

The order class class contains a list (detail) of Orderlines and the Orderline class a reference to a Product and so on. A very object oriented design by all means.

I need to create a generic procedure that takes a list of objects and a string specification and then finds all the joins. E.g AddToDataTableWithJoins(DataTable table, object[] objects, string specification).

If there exists two orders in the array, each with three orderlines the result would be a datatable with 6 rows.

e.g

{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}

But then again, the Order class may have more than one list (detail) and I must admit, that even though I'm familiar with reflection and simple recursion I'm at a loss on this on.

Any advice on how to create this algorithm is greatly appreciated.

Ideas

A restriction must be implemented so that no more than one list exists in each level of the specification, and no list exists in a different branch. e.g If the Customer class has defined a list of Order objects the following specification cannot be allowed: "ID;Date;Customer.ID;Customer.Orders.ID;Orderlines.Product.ID".

Then I believe, the following approach must be used:

  1. Determine the branch that contains one or more one-to-many relationships.
  2. Traverse every root object in the collection (the Order objects).
  3. For every property in the root object, store the values of every property not involved in the one-to-many relationships in an array.
  4. Use recursion and traverse every object in the child collection copying the array.
  5. When reaching the outermost 'node' add a new row in the DataTable.

These points may be revised as they are only thoughts at this point, but I think I'm close to something.

Thanks,
Stefan

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

梦巷 2024-08-24 03:47:58

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

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.
}

This sounds to me more like a flattening projection than either a union or a join. If this is the case, you should be able to do something like this:

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 }

(I left out some properties in the projection, but you should get the general idea)

This will give you an IEnumerable of an anonymous type, and you can now loop through it to print out the data (or whatever you want to do):

foreach(var item in q)
{
    Console.Write(item.Id);
    Console.Write(item.Date);
    // etc.
}
风透绣罗衣 2024-08-24 03:47:58

粗略的概述,这是伪代码:

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
}

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

A rough outline, this is pseudo code:

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
}

You might also want to add error checking / handling for if types of later object's fields mismatch or fields are not present (!) or objects are null. And you might want to add type check assertions as you proceed through the rows.

The code could search through all objects til it finds a non-NULL field for a column, to infer column type from (if you want to start supporting NULLs). Bear in mind that the type cannot be set up for a field if it is NULL in all rows as the routine then has nothing to infer type from. If you need to suport NULLs you may need to supply an array of types, or default an all-NULL column to type string or something.

Edit: Reformatted source code. Changed typeof call to call to getTypeForPath().

Edit: You added the requirement to do a SQL-join-like operation, basically where a data path includes a one-to-many join to repeat the row for each of child objects in the array for the one-to-many relationship. Presumably if there are several you want to sort by left-most one-to-many relationship first, then the second left-most etc.

Something like this, I suggest. As I said before this is just pseudo-code, and I'm really trying to illustrate the shape of the function and an approach, as its quite a hard problem, not write it for you. The following code probably contains errors and probably has a few mistakes in it:

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
}

Edit: Added "and probably has a few mistakes in it" :-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文