¿Propiedades de navegación de carga de carga asincrónica de entidades separadas de auto-seguimiento a través de un servicio WCF?

StackOverflow https://stackoverflow.com/questions/5875271

Pregunta

Tengo un cliente WCF que pasa entidades auto-seguimiento a una aplicación WPF construida con MVVM. La aplicación en sí tiene una interfaz dinámica. Los usuarios pueden seleccionar qué objetos desean visibles en su área de trabajo dependiendo del papel en el que se encuentren o en qué tarea están haciendo.

Mis entidades de auto-seguimiento tienen bastantes propiedades de navegación, y muchas de ellas no son necesarias. Dado que algunos de estos objetos pueden ser bastante grandes, solo me gustaría cargar estas propiedades a pedido.

Mi aplicación se ve así:

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

Mis modelos son entidades de auto-seguimiento. El repositorio del lado del cliente conecta un método LazyLoad (si es necesario) antes de devolver el modelo al Modelo ViewModel que lo solicitó. Todas las llamadas de servicio de WCF son asíncronas, lo que significa que los métodos de LazyLoad también son asíncronos.

La implementación real de LazyLoad me está dando algunos problemas. Estas son las opciones que me he encontrado.

Editar: eliminé las muestras de código para tratar de hacer que esto sea más fácil de leer y comprender. Vea la versión anterior de la pregunta si desea verla

Opción A

Liberidad asíncronamente lazyLAY COMPLEZA DEL MODELO desde el servidor WCF en el Getter

Bueno: Cargar datos a pedido es extremadamente simple. La unión en el XAML carga los datos, por lo que si el control está en la pantalla, los datos se carga asynchronsly y notifica la interfaz de usuario cuando está allí. Si no, nada se carga. Por ejemplo, <ItemsControl ItemsSource="{Binding CurrentConsumer.ConsumerDocuments}" /> Cargará los datos, sin embargo, si la sección de documentos de la interfaz no está allí, no se carga nada.

Malo: No se puede usar esta propiedad en ningún otro código antes de que se haya iniciado porque devolverá una lista vacía. Por ejemplo, la siguiente llamada siempre devolverá falso si los documentos no se han cargado.

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

Opción B

Haga manualmente una llamada para cargar datos cuando sea necesario

Bueno: Fácil de implementar: solo agregar LoadConsumerDocumentsSync() y LoadConsumerDocumentsAsync() métodos

Malo: Debe recordar cargar los datos antes de intentar acceder a él, incluso cuando se usa en enlaces. Esto puede parecer simple, pero puede salir de control rápidamente. Por ejemplo, cada consumo de consumo tiene un usuarios y userlastmodified. Hay una placa de datos que define el Usermodel con una información sobre herramientas que muestra datos de usuarios adicionales como extensión, correo electrónico, equipos, roles, etc. Entonces en mi ViewModel que muestra documentos que tendría que llamar LoadDocuments, luego atraviesalos y llama LoadConsumerModified y LoadConsumerCreated. También podría seguir adelante ... después de eso, tendría que LoadUserGroups y LoadUserSupervisor. También corre el riesgo de bucles circulares donde algo como un User tiene un Groups[] propiedad y un Group tiene un Users[] propiedad

Opción C

Mi opción favorita hasta ahora ... crea dos formas de acceder a la propiedad. Una sincronización y una asíncrata. Los enlaces se harían a la propiedad Async y cualquier código usaría la propiedad Sync.

Bueno: Los datos se cargan asincrónicamente según sea necesario, exactamente lo que quiero. Tampoco hay tanta codificación adicional, ya que todo lo que necesitaría hacer es modificar la plantilla T4 para generar estas propiedades/métodos adicionales.

Malo: Tener dos formas de acceder a los mismos datos parece ineficiente y confuso. Debería recordar cuándo debe usar Consumer.ConsumerDocumentsAsync en vez de Consumer.ConsumerDocumentsSync. También existe la posibilidad de que la llamada de servicio WCF se ejecute varias veces, y esto requiere una propiedad isobierta adicional para cada propiedad de navegación, como IsConsumerDocumentsLoaded.

Opción D

Omita la carga asíncrona y simplemente cargue todo sincrónicamente en los setters.

Bueno: Muy simple, no se necesita trabajo adicional

Malo: Bloquearía la interfaz de usuario cuando se carguen los datos. No quiero esto.

Opción E

Ten a alguien, así que dime que hay otra forma de hacer esto y señalarme a las muestras de codificación :)

Otras notas

Algunas de las generaciones de navegación se cargarán en el servidor WCF antes de devolver el objeto al cliente, sin embargo, otros son demasiado caros para hacerlo.

Con la excepción de llamar manualmente los eventos de carga en la opción C, todos se pueden hacer a través de la plantilla T4, por lo que hay muy poca codificación para mí. Todo lo que tengo que hacer es conectar el evento LazyLoad en el repositorio del lado del cliente y señalarlo a las llamadas de servicio correctas.

¿Fue útil?

Solución 3

La solución que se me ocurrió fue modificar la plantilla T4 para las entidades de auto-seguimiento para hacer los cambios que se muestran a continuación. La implementación real se ha omitido para que esto sea más fácil de leer, pero los nombres de propiedad/método deberían dejar claro qué hace todo.

Propiedades de navegación generadas por T4 antiguas

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

private MyClass _myProperty;

Nuevas propiedades de navegación generadas por 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();

Creé tres copias de la propiedad, que apuntan a la misma propiedad privada. La copia interna es para EF. Probablemente podría deshacerme de él, pero es más fácil dejarlo, ya que EF espera una propiedad con ese nombre y es más fácil dejarla que arreglar EF para usar un nuevo nombre de propiedad. Es interno ya que no quiero nada fuera del espacio de nombres de la clase para usarlo.

Las otras dos copias de la propiedad se ejecutan exactamente de la misma manera una vez que se ha cargado el valor, sin embargo, cargan la propiedad de manera diferente.

La versión async se ejecuta LoadMyPropertyAsync(), que simplemente funciona GetMyPropertyAsync(). Necesitaba dos métodos para esto porque no puedo poner el async Modificador en un Getter, y necesito devolver un vacío si llamo desde un método no asíncrono.

Se ejecuta la versión de sincronización GetMyPropertySync() que a su vez funciona GetMyPropertyAsync() sincrónicamente

Dado que todo esto es generado por T4, no necesito hacer nada, excepto conectar el delegado de carga perezosa async cuando la entidad se obtiene del servicio WCF.

Mis enlaces apuntan a la versión Async de la propiedad y cualquier otro código apunta a la versión de sincronización de la propiedad y ambos funcionan correctamente sin ninguna codificación adicional.

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

CurrentConsumer.DocumentsSync.Clear();

Otros consejos

Lo pensé, en primer lugar, tengo que decir que debe proporcionar una solución clara para el lector a este problema, depender de las propias de la propiedad que se cargan async cuando se une al usuario. La propiedad de Documents puede estar bien, pero está bastante cerca de un efecto secundario basado en un efecto secundario. solución. Si decimos que tal comportamiento a la vista está bien, debemos mantener nuestro código de descanso muy claro sobre las intenciones de TI, para que podamos ver cómo estamos tratando de acceder a los datos: asíncea o sincronización a través de algún nombre detallado de algo (método, nombre de clase, smth más).

Por lo tanto, creo que podríamos usar una solución que esté cerca del enfoque antiguo .sSynChronized (), crear una clase de decorador y proporcionar a cada propiedad un método de asyncload & syncload privado/protegido, y una clase de decorador sería sincronizada o una versión asíncrata de cada perezoso carga. clase, lo que sea más apropiado.

Cuando decora su clase con Sync Decorator, envuelve cada clase de lazyloadable en el interior con un decorador de sincronización también, por lo que podrá usar Synchuser (usuario) .Documentos. Contra en la versión de clase de sincron ) .Syncdocuments (documentos) .Conse detrás en la versión sobrecargada de la propiedad de documentos y llamaría a la función de getter sincronizada.

Dado que las versiones de sincronización y async funcionarán en el mismo objeto, este enfoque no conducirá a modificar algún objeto no referenciado en ningún otro lugar si desea modificar cualquier propiedad.

Su tarea puede sonar como una que se puede resolver de una manera mágica "hermosa y simple", pero no creo que pueda, o que no sea más simple que esta.

Si esto no funciona, todavía estoy 100% seguro de que necesita una forma clara de diferir en código si se usa una versión de clase de sincronización o asíncrata de clase o tendrá una base de código muy difícil de mantener.

Opción A debería ser la solución.

Crear una propiedad nombrada Cargingstatus La indicación de los datos se carga o se carga aún no se carga. Cargue los datos de forma asincrónica y establezca la propiedad LoadingStatus en consecuencia.

Verifique el estado de carga en cada propiedad y si los datos no están cargados, llame a la función para cargar datos y viceversa.

¿Podría el Binding.IsAsync ¿La propiedad de la biblioteca será útil aquí?

EDITAR: Expandir un poco ... Tenga una propiedad sincrónica cargada que llame al servicio WCF en primer uso. Luego, la encuadernación de asíncrono evitará que la interfaz de usuario bloquee.

Si bien se hizo esta pregunta hace un tiempo, está cerca de la parte superior de la lista de palabras clave de Async-Await y creo que sería respondida de manera bastante diferente en .NET 4.5.

Creo que este sería un caso de uso perfecto para el AsyncLazy<T> Tipo descrito en varios sitios:

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

Tengo dos pensamientos en mi cabeza.

1) Implementar un IQueryable<> respuesta en el WCF Servicio. Y siga hasta el DB con un IQueryable<> patrón.

2) En el repositorio del cliente, establezca el Getter en el ConsumerDocuments propiedad para obtener los datos.

private IEnumerable<ConsumerDocuments> _consumerDocuments;

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

Mientras lo veo, ViewModel debe ser consciente si hay datos disponibles o no. Puede ocultar o deshabilitar elementos de interfaz de usuario sin sentido sin datos mientras se obtienen los datos, luego mostrarlos cuando llegan los datos.

Detectas que necesitas cargar en algunos datos, por lo que establece la interfaz de usuario en modo "Esperando", inicia la búsqueda de Async, luego, cuando los datos entran del modo de espera. Quizás al hacer que ViewModel se suscriba a un evento "LoadCompleted" en el objeto que está interesado.

(editar) Puede evitar cargas excesivas o dependencias circulares realizando un seguimiento del estado de cada objeto modelo: descargado/cargando/cargado.

Aquí hay una opción E para usted.

Carga de datos asincrónicos. Haga que las cosas iniciales de la cola de búsqueda en un hilo de fondo AA que llene los objetos completos lentamente. Y haga que los métodos que requieran que los datos se carguen detrás de escena bloqueen el acabado de carga. (Bloqueo y pídales que notifiquen el hilo de fondo que los datos que necesitan son de alta prioridad, obténlos a continuación, para que pueda desbloquear lo antes posible).

Esto le da una interfaz de usuario que responde de inmediato cuando puede ser, la capacidad de escribir su código y no pensar en lo que se ha cargado, y principalmente funcionará. El único Gotcha es que ocasionalmente hará una llamada de bloqueo mientras los datos se cargan, sin embargo, con suerte no lo hará con demasiada frecuencia. Si lo hace, en el peor de los casos se degrada a algo así como la opción C donde tiene una búsqueda de datos de bloqueo y la capacidad de sondear para ver si está allí. Sin embargo, la mayoría de las veces no tendrías que preocuparte demasiado por eso.

Descargo de responsabilidad: personalmente no uso Windows y paso la mayor parte de mi tiempo trabajando en los extremos de la UI. Si te gusta la idea, siéntete libre de probarla. Pero en realidad no he seguido esta estrategia para nada más complicado que algunos detrás de escena que Ajax llama en una página web dinámica.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top