22

我有一个 WCF 客户端,它将自我跟踪实体传递给使用 MVVM 构建的 WPF 应用程序。应用程序本身有一个动态界面。用户可以根据他们所处的角色或他们正在执行的任务来选择他们希望在其工作区中显示哪些对象。

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

我的应用程序如下所示:

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

我的模型是自我跟踪实体。客户端存储库在将模型返回给请求它的 ViewModel 之前连接了一个 LazyLoad 方法(如果需要)。所有 WCF 服务调用都是异步的,这意味着 LazyLoad 方法也是异步的。

LazyLoad 的实际实现给我带来了一些麻烦。这是我想出的选项。

编辑 - 我删除了代码示例以尝试使其更易于阅读和理解。如果您想查看,请查看以前版本的问题

选项 A

在 Getter 中从 WCF 服务器异步延迟加载模型的属性

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

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

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

选项 B

需要时手动调用加载数据

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

不好:必须记住在尝试访问数据之前加载数据,包括在绑定中使用数据时。这可能看起来很简单,但它很快就会失控。例如,每个 ConsumerDocument 都有一个 UserCreated 和 UserLastModified。有一个 DataTemplate 定义了带有 ToolTip 的 UserModel,其中显示了额外的用户数据,例如扩展名、电子邮件、团队、角色等。因此,在显示文档的 ViewModel 中,我必须调用LoadDocuments,然后遍历它们并调用LoadConsumerModifiedand LoadConsumerCreated。它也可以继续下去……在那之后我不得不LoadUserGroupsLoadUserSupervisor。它还存在循环循环的风险,其中 aUser有一个Groups[]属性,而 aGroup有一个Users[]属性

选项 C

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

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

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

选项 D

跳过异步加载,只需在设置器中同步加载所有内容。

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

不好:数据加载时会锁定 UI。不想要这个。

选项 E

让某人告诉我还有另一种方法可以做到这一点并指出我的代码示例:)

其他注意事项

一些 NavigationProperties 将在将对象返回给客户端之前加载到 WCF 服务器上,但是其他的太昂贵而无法使用。

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

4

8 回答 8

3

考虑一下,首先我不得不说,你必须为这个问题提供一个清晰的读者解决方案,当你绑定到 User.Documents 属性时异步加载 DependecyProperties 可以,但它非常接近基于副作用解决方案。如果我们说 View 中的这种行为是可以的,我们必须让我们的其余代码非常清楚它的意图,这样我们才能看到我们是如何尝试访问数据的——通过一些冗长的命名(方法、类名、smth)来异步或同步别的)。

所以我认为我们可以使用一个接近旧 .AsSynchronized() 方法的解决方案,创建一个装饰器类,并为每个属性提供一个私有/受保护的 AsyncLoad 和 SyncLoad 方法,并且装饰器类将是每个延迟加载的同步或异步版本类,任何更合适的。

当你用 Sync 装饰器装饰你的类时,它也会用 Sync 装饰器将每个lazyloadable类包装在里面,这样你就可以在同步类版本上使用 SynchUser(User).Documents.Count 而没有问题,因为它会像 SynchUser(user ).SyncDocuments(Documents).Count 落后于 Documents 属性的重载版本,并会调用同步 getter 函数。

由于同步和异步版本都将在同一个对象上运行,因此如果您想修改任何属性,这种方法不会导致修改其他任何地方的未引用对象。

您的任务听起来可以通过某种神奇的“美丽而简单”的方式解决,但我认为它不能,或者它不会比这个更简单。

如果这不起作用,我仍然 100% 确定您需要一种清晰的方法来区分代码是否使用同步或异步版本的类,否则您将很难维护代码库。

于 2011-05-08T07:31:11.393 回答
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 以使用新属性名称更容易保留它。它是内部的,因为我不希望类名称空间之外的任何东西使用它。

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

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

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

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

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

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

CurrentConsumer.DocumentsSync.Clear();
于 2011-05-10T19:18:48.493 回答
1

选项 A应该是解决方案。

创建一个名为LoadingStatus的属性,指示数据已加载或加载尚未加载。异步加载数据并相应地设置 LoadingStatus 属性。

检查每个属性的加载状态,如果数据未加载,则调用函数加载数据,反之亦然

于 2011-05-11T10:39:40.207 回答
1

Binding.IsAsync图书馆财产在这里有用吗?

编辑:扩展一点.. 有一个延迟加载的同步属性,它将在第一次使用时调用 WCF 服务。然后异步绑定将阻止 UI 阻塞。

于 2011-06-06T13:08:48.467 回答
1

虽然这个问题是不久前提出的,但它位于 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

于 2013-09-04T15:51:46.100 回答
0

我脑子里有两个想法。

1) 在服务上实现IQueryable<>响应。WCF并按照IQueryable<>模式一直到数据库。

2) 在客户端存储库中设置ConsumerDocuments属性上的 getter 以获取数据。

private IEnumerable<ConsumerDocuments> _consumerDocuments;

public IEnumerable<ConsumerDocuments> ConsumerDocuments
{
    get
    {
        return _consumerDocuments ?? (_consumerDocuments = GetConsumerDocuments() );
    }
}
于 2011-05-06T19:08:02.743 回答
0

在我看来,ViewModel 需要知道是否有可用的数据。您可以在获取数据时隐藏或禁用没有数据就没有意义的 UI 元素,然后在数据到达时显示它们。

您检测到需要加载一些数据,因此将 UI 设置为“等待”模式,启动异步获取,然后当数据进入时将其退出等待模式。也许通过让 ViewModel 订阅它感兴趣的对象上的“LoadCompleted”事件。

(编辑)您可以通过跟踪每个模型对象的状态来避免过度加载或循环依赖:卸载/加载/加载。

于 2011-05-06T20:56:43.437 回答
0

这是给你的选项E。

异步加载数据。让初始获取队列在后台线程中排队,该线程缓慢地填充完整对象。并使任何需要在后台加载数据的方法在加载完成时被阻塞。(阻塞让他们通知后台线程他们需要的数据是高优先级的,接下来获取它们,这样您就可以尽快解除阻塞。)

这为您提供了一个在可能时立即响应的 UI,编写代码而不考虑已加载的内容的能力,并且它大部分都可以正常工作。一个问题是,有时您会在数据加载时进行阻塞调用,但希望它不会经常这样做。如果你这样做了,那么在最坏的情况下,你会降级到类似于选项 C 的东西,在这种情况下,你既可以阻塞获取数据,又可以轮询以查看它是否存在。但是大多数时候你不必担心太多。

免责声明:我个人不使用 Windows,并且大部分时间都在远离 UI 的后端工作。如果您喜欢这个想法,请随时尝试。但我实际上并没有遵循这种策略来处理比动态网页中的一些幕后 AJAX 调用更复杂的事情。

于 2011-05-07T00:49:23.380 回答