2

シナリオ

指定されたオブジェクトのリストからデータのテーブルを作成する必要があります。例では、Order オブジェクトのリスト/配列と、データ テーブルに何を含める必要があるかを詳細に示す文字列仕様があります (例: "ID;Date;Customer.ID;Customer.Name;Orderlines.Product.ID;Orderlines.Quantity;Orderlines.単価"。

注文クラス クラスには、注文行のリスト (詳細) が含まれ、注文行クラスには製品への参照などが含まれます。非常にオブジェクト指向の設計です。

オブジェクトのリストと文字列仕様を取得し、すべての結合を検索する汎用プロシージャを作成する必要があります。例: AddToDataTableWithJoins(DataTable テーブル、object[] オブジェクト、文字列仕様)。

配列に 2 つの注文があり、それぞれに 3 つの注文行がある場合、結果は 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. 1 つ以上の 1 対多の関係を含むブランチを決定します。
  2. コレクション内のすべてのルート オブジェクト (Order オブジェクト) をトラバースします。
  3. ルート オブジェクトのすべてのプロパティについて、1 対多の関係に関係しないすべてのプロパティの値を配列に格納します。
  4. 再帰を使用して、配列をコピーする子コレクション内のすべてのオブジェクトをトラバースします。
  5. 最も外側の「ノード」に到達したら、DataTable に新しい行を追加します。

これらの点は現時点での考えなので修正されるかもしれませんが、私は何かに近いと思います.

ありがとう、ステファン

4

2 に答える 2

1

これは、union や join というよりは、flattening プロジェクションのように思えます。この場合、次のようなことができるはずです。

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 をサポートする必要がある場合は、型の配列を指定するか、デフォルトですべて NULL の列を文字列などに設定する必要があります。

編集:再フォーマットされたソースコード。typeof 呼び出しを getTypeForPath() への呼び出しに変更しました。

編集: SQL 結合のような操作を実行する要件を追加しました。基本的に、データ パスには、1 対多の関係の配列内の子オブジェクトごとに行を繰り返すための 1 対多の結合が含まれます。おそらく、最初に左端の 1 対多の関係でソートしたいものがいくつかある場合、次に左から 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 = ...

  // 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 に答える