Я преобразовываю электронную таблицу Excel в список «Элементов» (это термин предметной области). Во время этого преобразования мне нужно пропустить строки заголовка и выбросить искаженные строки, которые невозможно преобразовать.
А теперь самое интересное. Мне нужно захватить эти искаженные записи, чтобы я мог о них сообщить. Я построил сумасшедший оператор LINQ (ниже). Это методы расширения, скрывающие беспорядочные операции LINQ с типами из библиотеки OpenXml.
var elements = sheet
.Rows() <-- BEGIN sheet data transform
.SkipColumnHeaders()
.ToRowLookup()
.ToCellLookup()
.SkipEmptyRows() <-- END sheet data transform
.ToElements(strings) <-- BEGIN domain transform
.RemoveBadRecords(out discard)
.OrderByCompositeKey();
Интересная часть начинается с ToElements
, где я преобразовываю поиск строки в свой список объектов домена (подробности: он называется ElementRow
, который позже преобразуется в Element
). Плохие записи создаются только с помощью ключа (индекса строки Excel) и однозначно идентифицируются по сравнению с реальным элементом.
public static IEnumerable<ElementRow> ToElements(this IEnumerable<KeyValuePair<UInt32Value, Cell[]>> map)
{
return map.Select(pair =>
{
try
{
return ElementRow.FromCells(pair.Key, pair.Value);
}
catch (Exception)
{
return ElementRow.BadRecord(pair.Key);
}
});
}
Затем я хочу удалить эти плохие записи (проще собрать все перед фильтрацией). Это метод RemoveBadRecords
, который начинался так ...
public static IEnumerable<ElementRow> RemoveBadRecords(this IEnumerable<ElementRow> elements)
{
return elements.Where(el => el.FormatId != 0);
}
Однако мне нужно сообщить об отброшенных элементах! И я не хочу запутывать свой метод расширения преобразования отчетами. Итак, я перешел к параметру out (принимая во внимание трудности использования параметра out в анонимном блоке)
public static IEnumerable<ElementRow> RemoveBadRecords(this IEnumerable<ElementRow> elements, out List<ElementRow> discard)
{
var temp = new List<ElementRow>();
var filtered = elements.Where(el =>
{
if (el.FormatId == 0) temp.Add(el);
return el.FormatId != 0;
});
discard = temp;
return filtered;
}
И вот! Я думал, что я хардкор и у меня все сработает одним выстрелом ...
var discard = new List<ElementRow>();
var elements = data
/* snipped long LINQ statement */
.RemoveBadRecords(out discard)
/* snipped long LINQ statement */
discard.ForEach(el => failures.Add(el));
foreach(var el in elements)
{
/* do more work, maybe add more failures */
}
return new Result(elements, failures);
Но в то время, когда я просматривал его, в моем discard
списке ничего не было! Я прошел через код и понял, что успешно создал полностью потоковое выражение LINQ.
- Временный список создан
Where
фильтр был назначен (но еще не запущен)- И список сброса был назначен
- Потом потоковое вещание было возвращено
Когда был повторен discard
, он не содержал элементов, потому что элементы еще не повторялись.
Есть ли способ решить эту проблему, используя созданную мной вещь? Должен ли я принудительно выполнять итерацию данных до или во время фильтрации плохих записей? Есть еще одна конструкция, которую я пропустил?
Некоторые комментарии
Джон упомянул, что задание / было / происходило. Я просто этого не ждала. Если я проверю содержимое discard
после итерации elements
, оно действительно заполнено! Итак, у меня на самом деле нет проблемы с назначением. Если только я не приму совет Джона о том, что хорошего / плохого должно быть в заявлении LINQ.