Программирование пользовательского объединения в C # (объединение множества объектов) - PullRequest
2 голосов
/ 06 января 2010

Сценарий

Мне нужно построить таблицу данных из списка объектов с учетом спецификации. Например, у меня есть список / массив объектов Order и спецификация строки, подробно описывающая, что должна содержать таблица данных, например "ID; Дата; Customer.ID; Customer.Name; Orderlines.Product.ID; Orderlines.Quantity; Orderlines. UnitPrice».

Класс класса заказа содержит список (данные) Orderlines, а класс Orderline - ссылку на Продукт и так далее. Очень объектно-ориентированный дизайн во всех отношениях.

Мне нужно создать общую процедуру, которая берет список объектов и спецификацию строки, а затем находит все объединения. E.g AddToDataTableWithJoins (таблица DataTable, объекты object [], спецификация строки).

Если в массиве существует два порядка, каждый с тремя линиями порядка, результатом будет таблица данных с 6 строками.

1011 * например *

{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. При достижении самого внешнего «узла» добавьте новую строку в таблицу данных.

Эти пункты могут быть пересмотрены, поскольку на данный момент они всего лишь мысли, но я думаю, что я близок к чему-то.

Спасибо, Stefan

Ответы [ 2 ]

1 голос
/ 06 января 2010

Для меня это больше похоже на сплющенную проекцию, чем на объединение или объединение. Если это так, вы можете сделать что-то вроде этого:

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.
}
0 голосов
/ 06 января 2010

Грубый контур, это псевдокод:

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, вам может потребоваться указать массив типов или по умолчанию для столбца all-NULL вводить строку или что-то в этом роде.

Редактировать : переформатированный исходный код. Изменен тип вызова для вызова 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
}

Редактировать : Добавлено "и, вероятно, есть несколько ошибок": -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...