通过WCF服务对分离的自我跟踪实体进行异步延迟加载导航属性?

Rac*_*hel 22 c# wcf lazy-loading self-tracking-entities async-await

我有一个WCF客户端,它将自我跟踪实体传递给使用MVVM构建的WPF应用程序.应用程序本身具有动态接口.用户可以根据他们在哪个角色或他们正在执行的任务来选择他们想要在他们的工作区域中可见的对象.

我的自我跟踪实体有很多导航属性,其中很多都不需要.由于其中一些对象可能非常大,我只想根据请求加载这些属性.

我的应用程序如下所示:

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

我的模型是自我跟踪实体.在将Model返回到请求它的ViewModel之前,Client-Side Repository挂钩了一个LazyLoad方法(如果需要).所有WCF服务调用都是异步的,这意味着LazyLoad方法也是异步的.

LazyLoad的实际实现给我带来了一些麻烦.以下是我提出的选项.

编辑 - 我删除了代码示例,试图让这更容易阅读和理解.如果您想查看它,请参阅以前版本的问题

选项A.

异步Lazy从Getter中的WCF服务器中获取Model的属性

好:按需加载数据非常简单.XAML中的绑定会加载数据,因此如果控件在屏幕上,则数据会异步加载并在UI处于通知状态时通知UI.如果没有,没有任何负载.例如,<ItemsControl ItemsSource="{Binding CurrentConsumer.ConsumerDocuments}" />将加载数据,但是如果接口的Documents部分不存在,则不会加载任何内容.

错误:在启动之前,不能在任何其他代码中使用此属性,因为它将返回一个空列表.例如,如果尚未加载文档,则以下调用将始终返回false.

public bool HasDocuments 
{ 
    get { return ConsumerDocuments.Count > 0; }
}
Run Code Online (Sandbox Code Playgroud)

选项B.

在需要时手动调用加载数据

好:易于实现 - 只需添加LoadConsumerDocumentsSync()LoadConsumerDocumentsAsync()方法

错误:必须记住在尝试访问数据之前加载数据,包括在Bindings中使用它时.这可能看起来很简单,但它很快就会失控.例如,每个ConsumerDocument都有一个UserCreated和UserLastModified.有一个DataTemplate定义UserModel,其中ToolTip显示其他用户数据,如扩展名,电子邮件,团队,角色等.因此,在我的ViewModel中显示我必须调用的文档LoadDocuments,然后循环遍历它们并调用LoadConsumerModifiedLoadConsumerCreated.它可以继续下去......之后我必须LoadUserGroupsLoadUserSupervisor.它还存在循环循环的风险,其中类似于User具有Groups[]属性的东西,并且Group具有Users[]属性

选项C.

到目前为止我最喜欢的选项...创建两种访问该属性的方法.一个同步和一个异步.绑定将对Async属性进行,任何代码都将使用Sync属性.

好:数据是根据需要异步加载的 - 正是我想要的.没有那么多额外的编码,因为我需要做的就是修改T4模板以生成这些额外的属性/方法.

不好:有两种方法来访问相同的数据似乎效率低下且令人困惑.您需要记住何时应该使用Consumer.ConsumerDocumentsAsync而不是Consumer.ConsumerDocumentsSync.WCF服务调用也有可能多次运行,这需要为每个导航属性提供额外的IsLoaded属性,例如IsConsumerDocumentsLoaded.

选项D.

跳过Asyncronous加载,只需在setter中同步加载所有内容.

好:很简单,不需要额外的工作

错误:在加载数据时会锁定UI.不要这个.

选项E.

有人在SO上告诉我还有另一种方法可以做到这一点,并指出代码示例:)

其他说明

在将对象返回到客户端之前,一些NavigationProperties将被加载到WCF服务器上,但是其他的太昂贵了.

除了手动调用选项C中的加载事件外,这些都可以通过T4模板完成,因此我无需编写任何代码.我所要做的就是在客户端存储库中连接LazyLoad事件并将其指向正确的服务调用.

Rac*_*hel 1

我想出的解决方案是修改自我跟踪​​实体的 T4 模板以进行如下所示的更改。为了更容易阅读,实际的实现已被省略,但属性/方法名称应该清楚地表明所有内容的作用。

旧 T4 生成的导航属性

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

private MyClass _myProperty;
Run Code Online (Sandbox Code Playgroud)

新的 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();
Run Code Online (Sandbox Code Playgroud)

我创建了该财产的三个副本,它们指向同一个私有财产。内部副本适用于 EF。我可能可以摆脱它,但最简单的方法是保留它,因为 EF 需要具有该名称的属性,并且保留它比修复 EF 以使用新的属性名称更容易。它是内部的,因为我不希望类名称空间之外的任何内容使用它。

加载值后,属性的其他两个副本将以完全相同的方式运行,但它们加载属性的方式不同。

异步版本运行LoadMyPropertyAsync(),它只是运行GetMyPropertyAsync()。为此,我需要两个方法,因为我无法将async修饰符放在 getter 上,并且如果从非异步方法调用,我需要返回 void。

同步版本运行,而同步版本又同步GetMyPropertySync()运行GetMyPropertyAsync()

由于这都是 T4 生成的,因此除了从 WCF 服务获取实体时连接异步延迟加载委托之外,我不需要做任何事情。

我的绑定指向属性的异步版本,任何其他代码都指向属性的同步版本,并且两者都可以正常工作,无需任何额外的编码。

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

CurrentConsumer.DocumentsSync.Clear();
Run Code Online (Sandbox Code Playgroud)