Асинхронно-ленивая загрузка навигации - PullRequest
22 голосов
/ 04 мая 2011

У меня есть клиент WCF, который передает объекты самообследования в приложение WPF, созданное с помощью MVVM. Само приложение имеет динамический интерфейс. Пользователи могут выбирать, какие объекты они хотят видеть в своей рабочей области, в зависимости от их роли или выполняемой задачи.

Мои сущности с самообследованием имеют довольно много навигационных свойств, и многие из них не нужны. Поскольку некоторые из этих объектов могут быть довольно большими, я хотел бы загружать эти свойства только по запросу.

Мое приложение выглядит так:

[WCF] <---> [ClientSide Repository] <---> [ViewModel] <---> [View]

Мои модели являются объектами с самостоятельным отслеживанием. Клиентский репозиторий подключает метод LazyLoad (при необходимости) перед возвратом модели в ViewModel, который ее запросил. Все вызовы службы WCF являются асинхронными, что означает, что методы LazyLoad также являются асинхронными.

Реальная реализация LazyLoad доставляет мне некоторые проблемы. Вот варианты, которые я придумал.

РЕДАКТИРОВАТЬ - я удалил примеры кода, чтобы их было легче читать и понимать. Смотрите предыдущую версию вопроса, если хотите ее увидеть

Вариант A

Асинхронно LazyLoad свойства модели с сервера WCF в Getter

Хорошо: Загрузка данных по запросу чрезвычайно проста. Привязка в XAML загружает данные, поэтому, если элемент управления находится на экране, данные загружаются асинхронно и уведомляют пользовательский интерфейс, когда он там есть. Если нет, ничего не загружается. Например, <ItemsControl ItemsSource="{Binding CurrentConsumer.ConsumerDocuments}" /> загрузит данные, однако, если раздела «Документы» интерфейса нет, ничего не загружается.

Плохо: Невозможно использовать это свойство в любом другом коде до его инициализации, поскольку оно возвратит пустой список. Например, следующий вызов всегда вернет false, если документы не были загружены.

public bool HasDocuments 
{ 
    get { return ConsumerDocuments.Count > 0; }
}

ВАРИАНТ B

Вручную сделать вызов для загрузки данных при необходимости

Хорошо: Простота реализации - просто добавьте LoadConsumerDocumentsSync() и LoadConsumerDocumentsAsync() методы

Плохо: Не забудьте загрузить данные, прежде чем пытаться получить к ним доступ, в том числе, когда они используются в привязках. Это может показаться простым, но может быстро выйти из-под контроля. Например, каждый ConsumerDocument имеет UserCreated и UserLastModified. Существует DataTemplate, который определяет UserModel с всплывающей подсказкой, отображающей дополнительные пользовательские данные, такие как добавочный номер, электронная почта, команды, роли и т. Д. Поэтому в моей ViewModel, которая отображает документы, мне нужно было бы вызвать LoadDocuments, затем просмотреть их и вызвать LoadConsumerModified и LoadConsumerCreated. Это может продолжаться тоже ... после этого мне придется LoadUserGroups и LoadUserSupervisor. Также существует риск возникновения циклических циклов, когда что-то вроде User имеет свойство Groups[], а Group имеет свойство Users[]

ОПЦИЯ C

Мой любимый вариант на данный момент ... создать два способа доступа к собственности. Одна синхронизация и одна асинхронная. Привязки будут сделаны к свойству Async, и любой код будет использовать свойство Sync.

Хорошо: Данные загружаются асинхронно по мере необходимости - именно то, что я хочу. Не так уж много дополнительного кодирования, так как все, что мне нужно сделать, это изменить шаблон T4 для генерации этих дополнительных свойств / методов.

Плохо: Наличие двух способов доступа к одним и тем же данным кажется неэффективным и запутанным. Вам нужно помнить, когда вы должны использовать Consumer.ConsumerDocumentsAsync вместо Consumer.ConsumerDocumentsSync. Существует также вероятность того, что вызов службы WCF будет выполнен несколько раз, и для этого требуется дополнительное свойство IsLoaded для каждого навигационного свойства, например IsConsumerDocumentsLoaded.

ОПЦИЯ D

Пропустите асинхронную загрузку и просто загрузите все синхронно в установщики.

Хорошо: Очень просто, дополнительная работа не требуется

Плохо: Блокирует интерфейс при загрузке данных. Не хочу этого.

ОПЦИЯ E

Пусть кто-нибудь на SO скажет мне, что есть другой способ сделать это, и укажет мне примеры кода:)

Другие заметки

Некоторые из NavigationProperties будут загружены на сервер WCF перед возвратом объекта клиенту, однако другие слишком дороги, чтобы сделать это с помощью.

За исключением ручного вызова событий Load в варианте C, все это может быть выполнено с помощью шаблона T4, поэтому для меня очень мало кода. Все, что мне нужно сделать, это подключить событие LazyLoad в клиентском репозитории и указать его на правильные вызовы службы.

Ответы [ 8 ]

3 голосов
/ 08 мая 2011

Подумайте об этом, прежде всего я должен сказать, что вы должны предоставить ясное для читателя решение этой проблемы: загрузка DependecyProperties асинхронной при привязке к свойству User.Documents может быть в порядке, но она довольно близка к решение на основе побочных эффектов. Если мы говорим, что такое поведение в представлении является нормальным, мы должны четко понимать наши намерения относительно остального кода, чтобы мы могли видеть, как мы пытаемся получить доступ к данным - асинхронно или синхронизируем с помощью какого-либо подробного именования чего-либо (метод, имя класса, smth еще).

Поэтому я думаю, что мы могли бы использовать решение, близкое к старому подходу .AsSynchronized (), создать класс декоратора и предоставить каждому свойству закрытый / защищенный метод AsyncLoad & SyncLoad, а классом декоратора была бы версия Sync или Async. каждого lazyloadable класса, что бы ни было более подходящим.

Когда вы декорируете свой класс с помощью Sync decorator, он оборачивает каждый lazyloadable класс внутри с помощью Sync decorator, так что вы сможете использовать SynchUser (User) .Documents.Count в версии класса синхронизации без пробников, потому что это будет что-то вроде SynchUser (пользователь) .SyncDocuments (документы). Считать в перегруженной версии свойства Documents и вызывать функцию получения синхронизации.

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

Ваша задача может звучать так, как будто она может быть решена каким-то волшебным «красивым и простым» способом, но я не думаю, что это возможно, или что она не будет более простой, чем эта.

Если это не сработает, я все еще на 100% уверен, что вам нужен четкий способ различения в коде, используется ли синхронная или асинхронная версия класса, или вам будет очень трудно поддерживать кодовую базу.

1 голос
/ 04 сентября 2013

Хотя этот вопрос был задан некоторое время назад, он находится в верхней части списка ключевых слов async-await, и я думаю, что в .net 4.5 на него ответят совсем по-другому.

Я считаю, что это был бы идеальный вариант использования для типа AsyncLazy<T>, описанного на нескольких сайтах:

http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html http://blog.stephencleary.com/2013/01/async-oop-3-properties.html

1 голос
/ 06 июня 2011

Может ли здесь быть полезным свойство библиотеки Binding.IsAsync?

Редактировать: немного расширить. Иметь синхронно загруженное свойство с отложенной загрузкой, которое будет вызывать службу WCF при первом использовании.Тогда асинхронное связывание будет препятствовать блокировке пользовательского интерфейса.

1 голос
/ 11 мая 2011

Вариант A должен быть решением.

Создайте одно свойство с именем LoadingStatus , указывающее, что данные загружены или загрузка еще не загружена. Загрузите данные асинхронно и соответственно установите свойство LoadingStatus.

Проверьте состояние загрузки в каждом свойстве и, если данные не загружены, вызовите функцию для загрузки данных и Viceversa .

1 голос
/ 10 мая 2011

Решение, которое я придумал, состояло в том, чтобы изменить шаблон T4 для объектов самоконтроля, чтобы внести изменения, показанные ниже. Реальная реализация была опущена, чтобы облегчить чтение, но имена свойств / методов должны прояснить, что все делает.

Старые сгенерированные T4 свойства навигации

[DataMember]
public MyClass MyProperty { get; set;}

private MyClass _myProperty;

Новые сгенерированные T4 свойства навигации

[DataMember]
internal MyClass MyProperty {get; set;}
public MyClass MyPropertySync {get; set;}
public MyClass MyPropertyAsync {get; set;}

private MyClass _myProperty;
private bool _isMyPropertyLoaded;

private async void LoadMyPropertyAsync();
private async Task<MyClass> GetMyPropertyAsync();
private MyClass GetMyPropertySync();

Я создал три копии объекта, которые указывают на одну и ту же частную собственность. Внутренняя копия для EF. Я мог бы, вероятно, избавиться от него, но его проще всего оставить, так как EF ожидает свойство с таким именем, и его легче оставить, чем исправить EF для использования нового имени свойства. Это внутреннее, так как я не хочу, чтобы что-то за пределами пространства имен класса использовало его.

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

В асинхронной версии запускается LoadMyPropertyAsync(), которая просто запускает GetMyPropertyAsync(). Для этого мне понадобилось два метода, потому что я не могу поместить модификатор async в геттер, и мне нужно возвращать пустоту, если вызывается из не асинхронного метода.

Версия Sync запускается GetMyPropertySync(), которая в свою очередь запускает GetMyPropertyAsync() синхронно

Поскольку все это сгенерировано T4, мне не нужно ничего делать, кроме как подключить асинхронный ленивый делегат загрузки, когда объект получен из службы WCF.

Мои привязки указывают на версию свойства Async, а любой другой код указывает на версию свойства Sync, и обе они работают правильно без какого-либо дополнительного кодирования.

<ItemsControl ItemsSource="{Binding CurrentConsumer.DocumentsAsync}" />

CurrentConsumer.DocumentsSync.Clear();
0 голосов
/ 07 мая 2011

Вот вам вариант E.

Асинхронная загрузка данных.Имейте начальную очередь выборки вещей в фоновом потоке, который заполняет полные объекты медленно.И сделайте так, чтобы любые методы, которые требуют загрузки данных за сценой, блокировали окончание загрузки.(Блокируя и , они уведомляют фоновый поток о том, что данные, которые им нужны, имеют высокий приоритет, получают их следующим, чтобы вы могли разблокировать как можно скорее.)

Это дает вам интерфейс, который немедленноотзывчивым, когда это возможно, умением писать свой код и не думать о том, что было загружено, и это в основном будет просто работать.Единственный недостаток в том, что иногда вы будете делать блокирующий вызов во время загрузки данных, однако, надеюсь, это не будет происходить слишком часто.Если вы это сделаете, то в худшем случае вы переходите к варианту C, где у вас есть как блокировка выборки данных, так и возможность опроса, чтобы увидеть, есть ли он там.Однако большую часть времени вам не придется сильно беспокоиться об этом.

Отказ от ответственности: я лично не пользуюсь Windows, и большую часть своего времени я работаю с серверными областями вдали от пользовательских интерфейсов.Если вам нравится идея, не стесняйтесь попробовать.Но на самом деле я не придерживался этой стратегии для чего-то более сложного, чем некоторые закулисные вызовы AJAX на динамической веб-странице.

0 голосов
/ 07 мая 2011

Как я вижу, ViewModel должен знать, есть ли доступные данные или нет.Вы можете скрыть или отключить элементы пользовательского интерфейса, которые не имеют смысла, без данных во время их выборки, а затем показать их при получении данных.

Вы обнаружите, что вам нужно загрузить некоторые данные, поэтому вы устанавливаете для пользовательского интерфейса значениеВ режиме ожидания отключите асинхронную выборку, а затем, когда данные поступят, выведите их из режима ожидания.Возможно, подписав ViewModel на событие «LoadCompleted» для интересующего его объекта.

(правка) Вы можете избежать чрезмерных нагрузок или циклических зависимостей, отслеживая состояние каждого объекта модели: Unloaded / Loading/Loaded.

0 голосов
/ 06 мая 2011

У меня в голове две мысли.

1) Реализация ответа IQueryable<> в службе WCF. И следуйте прямо до БД с шаблоном IQueryable<>.

2) В клиентском репозитории установите метод get для свойства ConsumerDocuments, чтобы получить данные.

private IEnumerable<ConsumerDocuments> _consumerDocuments;

public IEnumerable<ConsumerDocuments> ConsumerDocuments
{
    get
    {
        return _consumerDocuments ?? (_consumerDocuments = GetConsumerDocuments() );
    }
}
...