通过WCF服务异步惰性加载分离的自跟踪实体的导航属性

本文关键字:跟踪 实体 属性 导航 分离 加载 服务 WCF 异步 通过 | 更新日期: 2023-09-27 17:54:45

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

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

我的应用程序是这样的:

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

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

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

EDIT -我删除了代码示例,以尝试使其更容易阅读和理解。

选择

在Getter

中从WCF服务器异步加载模型的属性

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

Bad:在初始化之前不能在任何其他代码中使用此属性,因为它将返回一个空列表。例如,如果没有加载文档,下面的调用将始终返回false。

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

选项B

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

好:简单实现-只需添加LoadConsumerDocumentsSync()LoadConsumerDocumentsAsync()方法

坏:必须记住在试图访问它之前加载数据,包括在绑定中使用它时。这可能看起来很简单,但它可能很快就会失控。例如,每个ConsumerDocument都有一个UserCreated和UserLastModified。有一个DataTemplate,它用一个工具提示来定义UserModel,显示额外的用户数据,如扩展名、电子邮件、团队、角色等。因此,在显示文档的ViewModel中,我必须调用LoadDocuments,然后循环它们并调用LoadConsumerModifiedLoadConsumerCreated。它也可以继续下去……然后是LoadUserGroupsLoadUserSupervisor。它也有循环的风险,比如UserGroups[]属性,GroupUsers[]属性

选项C

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

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

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

选项D

跳过异步加载,在setter中同步加载所有内容。

Good:非常简单,不需要额外的工作

Bad:将在数据加载时锁定UI。不要这样。

选项E

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

其他Notes

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

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

通过WCF服务异步惰性加载分离的自跟踪实体的导航属性

给它一些思考,首先我不得不说,你必须提供一个清晰的读者解决方案,这个问题,当你绑定到User时,dependdecyproperties被异步加载。文档属性可以,但它非常接近于基于副作用的解决方案。如果我们说这样的行为在视图中是可以的,我们必须保持我们的其余代码非常清楚它的意图,所以我们可以看到我们是如何试图访问数据-异步或同步通过一些冗长的命名(方法,类名,史密斯其他)。

所以我认为我们可以使用一个解决方案,接近旧的。assynchronized()方法,创建一个装饰器类,并为每个属性提供一个私有/受保护的AsyncLoad &方法,并且装饰器类将是每个可惰性加载类的同步或异步版本,无论哪种方式更合适。

当你用Sync装饰器装饰你的类时,它也用Sync装饰器包装了每个懒加载类,所以你将能够使用SynchUser(User). documents。指望同步类版本没有问题,因为它将是类似的SynchUser(用户).SyncDocuments(文档)。在文档属性的重载版本中计数落后,并将调用同步getter函数。

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

你的任务可能听起来像是一个可以在一些神奇的"美丽&简单的方法,但我不认为它可以,或者它不会比这个更简单。

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

选项一个应该解决方案。

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

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

我想到的解决方案是修改自跟踪实体的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服务获得时,除了连接异步延迟加载委托外,我不需要做任何事情。

我的绑定指向该属性的Async版本,而任何其他代码指向该属性的Sync版本,两者都能正常工作,而无需任何额外的编码。

<ItemsControl ItemsSource="{Binding CurrentConsumer.DocumentsAsync}" />
CurrentConsumer.DocumentsSync.Clear();

Binding.IsAsync库属性在这里有帮助吗?

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

虽然这个问题在一段时间前被问到,但它接近async-await关键字列表的顶部,我认为在。net 4.5中将得到完全不同的答案。

我相信这将是AsyncLazy<T>类型的一个完美的用例,在几个网站上描述:

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

我脑子里有两个想法。

1)在WCF服务上实现一个IQueryable<>响应。然后用IQueryable<>模式跟踪到DB。

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

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

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

你检测到你需要加载一些数据,所以你将UI设置为"等待"模式,启动异步获取,然后当数据进入时将其从等待模式中取出。也许可以让ViewModel订阅它感兴趣的对象上的"LoadCompleted"事件。

您可以通过跟踪每个模型对象的状态来避免过度加载或循环依赖:Unloaded/Loading/Loaded。

这里有一个选项E。

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

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

免责声明:我个人不使用Windows,我的大部分时间都花在远离美国的后端上。如果你喜欢这个主意,不妨试试。但是,除了动态网页中的一些幕后AJAX调用之外,我还没有在任何更复杂的情况下遵循此策略。