Подтвердить что ты не робот

Асинхронно Lazy-Loading Навигационные свойства отдельных самонаблюдающих объектов через службу WCF?

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

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

Мое приложение выглядит следующим образом:

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

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

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

EDIT - я удалил образцы кода, чтобы попытаться сделать это проще для чтения и понимания. См. Предыдущую версию вопроса, если вы хотите увидеть ее

Вариант A

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

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

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

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

ОПЦИЯ B

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

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

Плохо:. Не забудьте загрузить данные, прежде чем пытаться получить к нему доступ, в том числе, когда он используется в Bindings. Это может показаться простым, но оно может быстро выйти из-под контроля. Например, каждый 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

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

Хорошо: Очень просто, без дополнительной работы

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

ВАРИАНТ E

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

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

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

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

4b9b3361

Ответ 1

Решение, с которым я столкнулся, состояло в том, чтобы изменить шаблон 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, чтобы использовать новое имя свойства. Он является внутренним, так как я не хочу, чтобы что-то вне пространства имен классов использовало его.

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

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

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

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

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

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

CurrentConsumer.DocumentsSync.Clear();

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

Может ли быть полезной функция Binding.IsAsync?

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

Ответ 5

Пока этот вопрос был задан некоторое время назад, он находится в верхней части списка ключевых слов async-wait, и я думаю, что в .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

Ответ 6

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

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

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

private IEnumerable<ConsumerDocuments> _consumerDocuments;

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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