Как отложить загрузку с помощью моего собственного IQueryable, который поддерживается списком

У меня есть список разрешений, определенных следующим образом:

private List<PermissionItem> permissionItems;
private ReadOnlyCollection<PermissionItem> permissionItemsReadOnly;

Этот список извлекается из веб-службы через фоновый поток. Версия только для чтения заполняется из версии списка.

Я выставляю этот список для остальной части моего (довольно большого) приложения следующим образом:

public IQueryable<PermissionItem> PermissionItems
{
   get
   {
       // Make sure that the permissions have returned.  
       // If they have not then we need to wait for that to happen.
       if (!doneLoadingPermissions.WaitOne(10000))
           throw new ApplicationException("Could not load permissions");

       return permissionItemsReadOnly.AsQueryable();
   }
}

Это все хорошо и хорошо. Пользователь может запросить разрешения и получить их после загрузки.

Но если у меня есть такой код в конструкторе (в другом классе):

ThisClassInstanceOfThePermisssions = SecurityStuff.PermissionItems;

Тогда я вполне уверен, что это заблокируется до тех пор, пока разрешения не вернутся. Но его не нужно блокировать, пока разрешения не будут фактически использованы.

Я читал, что IQueryable - это "ленивая загрузка". (Я использовал эту функцию в своем коде Entity Framework.)

Можно ли изменить это, чтобы разрешить ссылки на мой IQueryable в любое время и блокировать только тогда, когда данные действительно используются?

Примечание. Это функция, которую приятно иметь. На самом деле загрузка разрешений не занимает много времени. Так что, если это запрос/выражение типа «свернуть свой собственный», то я, вероятно, пропущу. Но мне интересно, что нужно, чтобы заставить его работать.


person Vaccano    schedule 08.08.2012    source источник
comment
Какое поведение вы хотите для возвращенного IQueryable? Что должно произойти, если список не полностью загружен?   -  person Thomas Levesque    schedule 09.08.2012
comment
@ThomasLevesque - я бы предпочел переместить свой код WaitOne туда, где фактически осуществляется доступ к данным в списке. И разрешить назначать ссылку (и, возможно, создавать подчиненные IQueryables) без блокировки.   -  person Vaccano    schedule 09.08.2012


Ответы (2)


Да, это возможно. Во-первых, вероятно, вам следует переключиться на IEnumerable, поскольку вы не используете никаких функций IQueryable. Далее вам нужно реализовать новый итератор:

public IEnumerable<PermissionItem> PermissionItems
{
   get
   {
        return GetPermissionItems();
   }
}
static IEnumerable<PermissionItem> GetPermissionItems()
{
       // Make sure that the permissions have returned.  
       // If they have not then we need to wait for that to happen.
       if (!doneLoadingPermissions.WaitOne(10000))
           throw new ApplicationException("Could not load permissions");

       foreach (var item in permissionItemsReadOnly) yield return item;
}

Событие будет ожидаться только в том случае, если вызывающая сторона свойства перечисляет IEnumerable. Просто возврат ничего не дает.

person usr    schedule 08.08.2012
comment
Он говорит, что предоставляет список остальной части приложения, а не по сети. В любом случае, IQueryable также не позволяет волшебным образом выполнять запросы по сети. - person usr; 09.08.2012
comment
@usr - это то, что я искал. Кажется, я никогда не думал об использовании yeild, но он позволяет кое-что хорошее! - person Vaccano; 09.08.2012
comment
@Vaccano, вы также можете реализовать IEnumerable вручную, но это ужасная задача. yield и ручные реализации имеют одинаковую выразительную силу. - person usr; 09.08.2012
comment
У @Vaccano J.Skeet есть отличные материалы по итераторам, вы можете получить очень подробное представление о том, как это работает. См. его ответ здесь. В любом случае +1 за полезный ответ. - person oleksii; 09.08.2012
comment
Как ни странно, ReSharper хочет, чтобы я изменил последнюю строку на return permissionItemsReadOnly;. Это то же самое, что и использование yield? - person Vaccano; 09.08.2012
comment
Нет, это заставит метод выполняться синхронно. R# применяет здесь полезную эвристику, но в данном случае она наносит ущерб. Выберите игнорировать с комментарием из меню лампочки, чтобы задокументировать свое намерение другим! - person usr; 09.08.2012

Взгляните на класс Lazy<T>.

Ленивая инициализация происходит при первом доступе к свойству Lazy.Value. Используйте экземпляр Lazy, чтобы отложить создание большого или ресурсоемкого объекта или выполнение ресурсоемкой задачи.

person oleksii    schedule 08.08.2012
comment
Это требует, чтобы вызывающие абоненты изменились. - person usr; 09.08.2012
comment
Я никогда не слышал об этом. Спасибо, что указали мне на это. Я думаю, что ответ usr - это то, что мне нужно, но я добавляю его в свой набор инструментов. - person Vaccano; 09.08.2012