Приведение IEnumerable‹TEntity› к IEnumerable‹TResult› с обеспечением отложенного выполнения

В моем приложении есть довольно много существующих «служебных команд», которые обычно возвращают List<TEntity>. Однако я написал их таким образом, что любые запросы не будут оцениваться до самого последнего оператора, когда они будут преобразованы ToList<TEntity> (или, по крайней мере, я так думаю).

Теперь мне нужно начать получать некоторую «контекстно-зависимую» информацию от команд, и я думаю сделать следующее:

  1. Сохраните существующие команды такими же, как сегодня, но убедитесь, что они возвращают IEnumerable<TEntity>, а не IList<TEntity>.
  2. Создайте новые команды, которые вызывают старые команды, но возвращают IEnumerable<TResult>, где TResult — это не сущность, а скорее модель представления, модель результата и т. д. — некоторое представление данных, полезное для приложения.

Первый случай, когда мне это понадобилось, — это поиск объекта Group. В моей схеме Groups идут со специфическими для User разрешениями, но выплевывать в свой результат весь список пользователей и разрешений мне не реально - во-первых, потому что пользователей может быть много, во-вторых, потому что разрешений много, и в-третьих, потому что эта информация не должна быть доступна недостаточно привилегированным пользователям (т.е. "гость" не должен видеть, что может делать "член").

Итак, я хочу иметь возможность взять результат исходной команды, IEnumerable<Group>, и описать, как каждое Group должно быть преобразовано в GroupResult при заданном вводе User (в данном случае Username).

Если я попытаюсь перебрать результат исходной команды с помощью ForEach, я знаю, что это приведет к принудительному выполнению результата и, следовательно, потенциально приведет к ненужному увеличению времени выполнения. Что, если бы я захотел дополнительно составить результат «новой» команды (которая возвращает GroupResult) и отфильтровать определенные группы? Тогда, возможно, я бы вычислил массу привилегий для введенного пользователя, только чтобы позже отфильтровать родительские объекты GroupResult!

Я предполагаю, что мой вопрос сводится к... как мне сообщить C#, как я хотел бы преобразовать каждый член IEnumerable, не обязательно делая это во время запуска метода?


person tacos_tacos_tacos    schedule 17.05.2014    source источник
comment
Нельзя ли просто использовать .Select(group => new GroupResult(...))?   -  person Iridium    schedule 17.05.2014


Ответы (2)


Чтобы лениво преобразовать перечисляемое из одного типа в другой, вы делаете следующее:

IEnumerable<TResult> result = source.Cast<TResult>();

Это предполагает, что элементы исходного перечисляемого могут быть приведены к TResult. Если они не могут, вам нужно использовать стандартную проекцию с .Select(x => ... ).

Кроме того, будьте осторожны, возвращая IEnumerable<T> из службы или базы данных, так как часто есть ресурсы, которые вам нужно открыть для получения данных, поэтому теперь вам нужно убедиться, что эти ресурсы открыты всякий раз, когда вы пытаетесь оценить перечисляемое. Держать соединение с базой данных открытым — плохая идея. Я был бы более склонен вернуть массив, который вы указали как IEnumerable<>.

Однако, если вы действительно хотите получить IEnmerable<> из службы или базы данных, которые действительно ленивы и будут автоматически обновлять данные, вам нужно попробовать «Интерактивные расширения» Microsoft Reactive Framework Team, чтобы помочь с этим.

У них есть приятное расширение IEnumerable<> под названием Using, которое делает "горячее" перечисление, открывающее ресурс для каждой итерации.

Это будет выглядеть примерно так:

var d =
    EnumerableEx
        .Using(
            () => new DB(),
            db => db.Data.Where(x => x == 2));

Он создает новый экземпляр DB каждый раз при повторении перечислимого и удаляет базу данных, когда перечисляемое будет завершено. Что-то стоящее рассмотрения.

Используйте NuGet и найдите «Ix-Main» для интерактивных расширений.

person Enigmativity    schedule 18.05.2014

Вы ищете команду yield return.

Когда вы определяете метод, возвращающий IEnumerable, и возвращаете его данные с помощью yield return, возвращаемое значение повторяется в методе-потребителе. Вот как это может выглядеть:

IEnumerable<GroupResult> GetGroups(string userName)
{
    foreach(var group in context.Groups.Where(g => <some user-specific condition>))
    {
        var result = new GroupResult()
        ... // Further compose the result.

        yield return result;

    }
}

При использовании кода:

var groups = GetGroups("tacos");

// At this point no eumeration has occurred yet. Any breakpoints in GetGroups
// have not been hit.

foreach(var g in groups)
{
    // Now iteration in GetGroups starts!
}
person Gert Arnold    schedule 17.05.2014