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

时间:2011-05-03 20:24:41

标签: 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; }
}

选项B

在需要时手动拨打电话加载数据

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

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

选项C

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

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

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

选项D

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

好:非常简单,无需额外工作

错误:在加载数据时会锁定用户界面。不要这样。

选项E

有人告诉我有另一种方法可以做到这一点,并指出我代码样本:)

其他笔记

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

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

8 个答案:

答案 0 :(得分:3)

给了它一些思考,首先我要说你必须为这个问题提供一个明确的读者解决方案,当你绑定到User.Documents属性时DependecyProperties被加载异步可以没问题,但它非常接近于基于副作用的解决方案如果我们说View中的这种行为是正确的,我们必须保持我们的休息代码非常清楚它的意图,所以我们可以看到我们如何尝试访问数据 - 异步或同步通过某些详细的命名(方法,类名,smth)其他人)。

所以我认为我们可以使用一个接近旧的解决方案.AsSynchronized()方法,创建一个装饰器类,并为每个属性提供一个私有/受保护的AsyncLoad&amp; SyncLoad方法和装饰器类将是每个lazyloadable类的Sync或Async版本,无论更适合什么。

当您使用Sync装饰器装饰您的类时,它将每个lazyloadable类包装在内部,同时也使用Sync装饰器,这样您就可以在同步类版本上使用SynchUser(User).Documents.Count而没有probs因为它会像smth一样 SynchUser(用户).SyncDocuments(Documents).Count属于Documents属性的重载版本,并将调用sync getter函数。

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

你的任务听起来可能是以一种神奇的“美丽和简单”的方式解决的,但我不认为它可以,或者它不会比这更简单。

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

答案 1 :(得分: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上,如果从非异步方法调用,我需要返回一个void。

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

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

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

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

CurrentConsumer.DocumentsSync.Clear();

答案 2 :(得分:1)

选项A 应该是解决方案。

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

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

答案 3 :(得分:1)

Binding.IsAsync库属性可以在这里有用吗?

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

答案 4 :(得分: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

答案 5 :(得分:0)

我脑子里有两个想法。

1)在IQueryable<>服务上实施WCF响应。然后使用IQueryable<>模式直接关注数据库。

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

private IEnumerable<ConsumerDocuments> _consumerDocuments;

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

答案 6 :(得分:0)

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

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

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

答案 7 :(得分:0)

这是您的选项E.

异步加载数据。将初始提取队列放在后台线程中,缓慢填充整个对象。并且任何需要在幕后加载数据的方法都会在加载完成时阻塞。 (阻止让他们通知后台线程他们需要的数据是高优先级,然后接下来,所以你可以尽快解锁。)

这为您提供了一个可以立即响应的用户界面,能够编写代码而无需考虑已加载的内容,而且它几乎可以正常工作。一个问题是偶尔你会在加载数据时进行阻塞调用,但希望它不会经常这样做。如果你这样做,那么在最坏的情况下你会降级到类似于选项C的东西,你可以同时获得数据的阻塞,以及轮询以查看它是否存在的能力。但是大部分时间你都不必过于担心它。

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