简体   繁体   English

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

[英]Asynchronously Lazy-Loading Navigation Properties of detached Self-Tracking Entities through a WCF service?

I have a WCF client which passes Self-Tracking Entities to a WPF application built with MVVM.我有一个 WCF 客户端,它将自我跟踪实体传递给使用 MVVM 构建的 WPF 应用程序。 The application itself has a dynamic interface.应用程序本身有一个动态界面。 Users can select which objects they want visible in their Work area depending on what role they are in or what task they are doing.用户可以 select 根据他们的角色或他们正在执行的任务,他们希望哪些对象在他们的工作区域中可见。

My self-tracking entities have quite a few Navigation Properties, and a lot of them are not needed.我的自跟踪实体有很多导航属性,其中很多是不需要的。 Since some of these objects can be quite large, I'd like to only load these properties on request.由于其中一些对象可能非常大,我只想在请求时加载这些属性。

My application looks like this:我的应用程序如下所示:

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

My Models are Self-Tracking Entities.我的模型是自我跟踪实体。 The Client-Side Repository hooks up a LazyLoad method (if needed) before returning the Model to the ViewModel that requested it.在将 Model 返回到请求它的 ViewModel 之前,客户端存储库会连接一个 LazyLoad 方法(如果需要)。 All WCF Service calls are asyncronous, which means the LazyLoad methods are also asyncronous.所有 WCF 服务调用都是异步的,这意味着 LazyLoad 方法也是异步的。

The actual implementation of the LazyLoad is giving me some trouble. LazyLoad 的实际实现给我带来了一些麻烦。 Here are the options I have come up with.这是我想出的选项。

EDIT - I removed the code samples to try and make this easier to read and understand.编辑 - 我删除了代码示例以尝试使其更易于阅读和理解。 See previous version of question if you want to see it如果您想查看,请查看以前版本的问题

Option A选项 A

Asynchronously LazyLoad the Model's properties from the WCF server in the Getter从 Getter 中的 WCF 服务器异步延迟加载模型的属性

Good: Loading data on demand is extremely simple.好:按需加载数据非常简单。 The binding in the XAML loads the data, so if the control is on the screen the data loads asynchronsly and notifies the UI when it's there. XAML 中的绑定会加载数据,因此如果控件在屏幕上,则数据会异步加载并在出现时通知 UI。 If not, nothing loads.如果没有,则不会加载任何内容。 For example, <ItemsControl ItemsSource="{Binding CurrentConsumer.ConsumerDocuments}" /> will load the data, however if the Documents section of the interface is not there then nothing gets loaded.例如, <ItemsControl ItemsSource="{Binding CurrentConsumer.ConsumerDocuments}" />将加载数据,但是如果界面的 Documents 部分不存在,则不会加载任何内容。

Bad: Cannot use this property in any other code before it has been initiated because it will return an empty list.错误:在启动之前无法在任何其他代码中使用此属性,因为它将返回一个空列表。 For example, the following call will always return false if documents have not been loaded.例如,如果尚未加载文档,则以下调用将始终返回 false。

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

OPTION B选项 B

Manually make a call to load data when needed需要时手动调用加载数据

Good: Simple to implement - Just add LoadConsumerDocumentsSync() and LoadConsumerDocumentsAsync() methods好:易于实现 - 只需添加LoadConsumerDocumentsSync()LoadConsumerDocumentsAsync()方法

Bad: Must remember to load the data before trying to access it, including when its used in Bindings.不好:必须记住在尝试访问数据之前加载数据,包括在绑定中使用数据时。 This might seem simple, but it can get out of hand quickly.这可能看起来很简单,但它很快就会失控。 For example, each ConsumerDocument has a UserCreated and UserLastModified.例如,每个 ConsumerDocument 都有一个 UserCreated 和 UserLastModified。 There is a DataTemplate that defines the UserModel with a ToolTip displaying additional User data such as extension, email, teams, roles, etc. So in my ViewModel that displays documents I would have to call LoadDocuments , then loop through them and call LoadConsumerModified and LoadConsumerCreated .有一个 DataTemplate 使用 ToolTip 定义 UserModel,其中显示其他用户数据,例如扩展、email、团队、角色等。因此,在显示文档的 ViewModel 中,我必须调用LoadDocuments ,然后遍历它们并调用LoadConsumerModifiedLoadConsumerCreated . It could keep going too... after that I'd have to LoadUserGroups and LoadUserSupervisor .它也可以继续下去......之后我必须LoadUserGroupsLoadUserSupervisor It also runs the risk of circular loops where something like a User has a Groups[] property, and a Group has a Users[] property它还存在循环循环的风险,其中User具有Groups[]属性,而Group具有Users[]属性

OPTION C选项 C

My favorite option so far... create two ways to access the property.到目前为止我最喜欢的选择...创建两种访问属性的方法。 One Sync and one Async.一个同步和一个异步。 Bindings would be done to the Async property and any code would use the Sync property.将对 Async 属性进行绑定,并且任何代码都将使用 Sync 属性。

Good: Data is loaded asynchronously as needed - Exactly what I want.好:数据根据需要异步加载 - 正是我想要的。 There isn't that much extra coding either since all I would need to do is modify the T4 template to generate these extra properties/methods.也没有太多额外的编码,因为我需要做的就是修改 T4 模板以生成这些额外的属性/方法。

Bad: Having two ways to access the same data seems inefficient and confusing.不好:有两种方法来访问相同的数据似乎效率低下且令人困惑。 You'd need to remember when you should use Consumer.ConsumerDocumentsAsync instead of Consumer.ConsumerDocumentsSync .您需要记住何时应该使用Consumer.ConsumerDocumentsAsync而不是Consumer.ConsumerDocumentsSync There is also the chance that the WCF Service call gets run multiple times, and this requires an extra IsLoaded property for every navigational property, such as IsConsumerDocumentsLoaded. WCF 服务调用也有可能多次运行,这需要每个导航属性都有一个额外的 IsLoaded 属性,例如 IsConsumerDocumentsLoaded。

OPTION D选项 D

Skip the Asyncronous loading and just load everything synchronously in the setters.跳过异步加载,只需在设置器中同步加载所有内容。

Good: Very simple, no extra work needed好:很简单,不需要额外的工作

Bad: Would lock the UI when data loads.不好:数据加载时会锁定 UI。 Don't want this.不想要这个。

OPTION E选项 E

Have someone on SO tell me that there is another way to do this and point me to code samples:)让某人告诉我还有另一种方法可以做到这一点并指出我的代码示例:)

Other Notes其他注意事项

Some of the NavigationProperties will be loaded on the WCF server prior to returning the object to the client, however others are too expensive to do that with.在将 object 返回给客户端之前,一些 NavigationProperties 将加载到 WCF 服务器上,但是其他的太昂贵而无法使用。

With the exception of manually calling the Load events in Option C, these can all be done through the T4 template so there is very little coding for me to do.除了手动调用选项 C 中的加载事件外,这些都可以通过 T4 模板完成,因此我几乎不需要编写代码。 All I have to do is hook up the LazyLoad event in the client-side repository and point it to the right service calls.我所要做的就是在客户端存储库中连接 LazyLoad 事件并将其指向正确的服务调用。

Gave it some thought, first of all I have to say that you must provide a clear to reader solution to this problem, DependecyProperties being loaded async when you bind to User.Documents property can be ok, but its pretty close to a side effect based solution.考虑一下,首先我不得不说,你必须为这个问题提供一个清晰的读者解决方案,当你绑定到 User.Documents 属性时异步加载 DependecyProperties 可以,但它非常接近基于副作用解决方案。 If we say that such behaviour in View is ok, we must keep our rest code very clear about it intentions, so we can see how are we trying to access data - async or sync via some verbose naming of something (method,classname,smth else).如果我们说 View 中的这种行为是可以的,我们必须让我们的 rest 代码非常清楚它的意图,这样我们就可以看到我们是如何尝试访问数据的——通过一些冗长的命名(方法、类名、smth)来异步或同步别的)。

So I think we could use a solution that is close to old.AsSynchronized() approach, create a decorator class, and provide each property a private/protected AsyncLoad & SyncLoad method, and a decorator class would be Sync or Async version of each lazyloadable class, whatever is more appropraite.所以我认为我们可以使用接近 old.AsSynchronized() 方法的解决方案,创建装饰器 class,并为每个属性提供私有/受保护的 AsyncLoad 和 SyncLoad 方法,装饰器 class 将是每个延迟加载的同步或异步版本class,更合适的。

When you decorate your class with Sync decorator it wraps each lazyloadable class inside with a Sync decorator too so you will be able to use SynchUser(User).Documents.Count on sync class version with no probs cause it will be smth like SynchUser(user).SyncDocuments(Documents).Count behind in overloaded version of Documents property and would call sync getter function. When you decorate your class with Sync decorator it wraps each lazyloadable class inside with a Sync decorator too so you will be able to use SynchUser(User).Documents.Count on sync class version with no probs cause it will be smth like SynchUser(user ).SyncDocuments(Documents).Count 落后于 Documents 属性的重载版本,并会调用同步 getter function。

Since both sync and async versions will be operating on same object this approach wont lead to modifying some non referenced anywhere else object if you want to modify any property.由于同步和异步版本都将在同一个 object 上运行,因此如果您想修改任何属性,这种方法不会导致修改其他任何地方未引用的 object。

Your task may sound as one that can be solved in some magic "beautiful & simple" way but I dont think it can, or that it wont be any more simple than this one.您的任务听起来可以通过某种神奇的“美丽而简单”的方式解决,但我认为它不能,或者它不会比这个更简单。

If this doesn't work im still 100% sure you need a clear way to differntiate in code whether a sync or async version of class is used or you will have a very hard to maintain code base.如果这不起作用,我仍然 100% 确定您需要一种清晰的方法来区分代码是使用 class 的同步版本还是异步版本,否则您将很难维护代码库。

While this question was asked a while ago, it is near the top of the async-await keyword list and I think would be answered quite differently in .net 4.5.虽然这个问题是不久前提出的,但它位于 async-await 关键字列表的顶部,我认为在 .net 4.5 中的回答会完全不同。

I believe this would be a perfect use case for the AsyncLazy<T> type described on several sites:我相信这将是几个站点上描述的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 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

The solution I came up with was to modify the T4 template for the self-tracking entities to make the changes shown below.我想出的解决方案是修改自跟踪实体的 T4 模板以进行如下所示的更改。 The actual implementation has been omitted to make this easier to read, but the property/method names should make it clear what everything does.为了便于阅读,省略了实际的实现,但属性/方法名称应该清楚地说明一切的作用。

Old T4 Generated Navigation Properties旧 T4 生成的导航属性

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

private MyClass _myProperty;

New T4 Generated Navigation Properties新的 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();

I created three copies of the property, which point to the same private property.我创建了该属性的三个副本,它们指向同一个私有属性。 The internal copy is for EF.内部副本用于 EF。 I could probably get rid of it, but its easiest to just leave it in since EF expects a property by that name and its easier to leave it than to fix up EF to use a new property name.我可能可以摆脱它,但最简单的方法是将其保留,因为 EF 期望具有该名称的属性,并且它比修复 EF 以使用新属性名称更容易保留它。 It is internal since I don't want anything outside of the class namespace to use it.它是内部的,因为我不希望 class 命名空间之外的任何东西使用它。

The other two copies of the property run the exact same way once the value has been loaded, however they load the property differently.加载值后,属性的其他两个副本以完全相同的方式运行,但是它们加载属性的方式不同。

The Async version runs LoadMyPropertyAsync() , which simply runs GetMyPropertyAsync() .异步版本运行LoadMyPropertyAsync() ,它只是运行GetMyPropertyAsync() I needed two methods for this because I cannot put the async modifier on a getter, and I need to return a void if calling from a non-async method.为此我需要两种方法,因为我不能将async修饰符放在 getter 上,如果从非异步方法调用,我需要返回一个 void。

The Sync version runs GetMyPropertySync() which in turn runs GetMyPropertyAsync() synchronously同步版本运行GetMyPropertySync() ,后者又同步运行GetMyPropertyAsync()

Since this is all T4-generated, I don't need to do a thing except hook up the async lazy load delegate when the entity is obtained from the WCF service.因为这都是 T4 生成的,所以当从 WCF 服务获取实体时,除了连接异步延迟加载委托之外,我不需要做任何事情。

My bindings point to the Async version of the property and any other code points to the Sync version of the property and both work correctly without any extra coding.我的绑定指向属性的异步版本,任何其他代码都指向属性的同步版本,并且两者都可以正常工作,无需任何额外的编码。

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

CurrentConsumer.DocumentsSync.Clear();

Option A should be the solution.选项 A应该是解决方案。

Create one property named LoadingStatus indicating data is loaded or loading not yet loaded.创建一个名为LoadingStatus的属性,指示数据已加载或加载尚未加载。 Load data asynchronously and set the LoadingStatus property accordingly.异步加载数据并相应地设置 LoadingStatus 属性。

Check the loading status in each property and if data not loaded then call function to load data and viceversa .检查每个属性的加载状态,如果数据未加载,则调用 function 加载数据,反之亦然

Could the Binding.IsAsync library property be helpful here? Binding.IsAsync库属性在这里有用吗?

Edit: expanding a little.. Have a lazy loaded synchronous property that will call the WCF service on first use.编辑:扩展一点.. 有一个延迟加载的同步属性,它将在首次使用时调用 WCF 服务。 Then the async binding will keep the UI from blocking.然后异步绑定将阻止 UI 阻塞。

I have two thoughts in my head.我脑子里有两个想法。

1) Implement an IQueryable<> response on the WCF Service. 1) 在WCF服务上实现IQueryable<>响应。 And follow right down to the DB with an IQueryable<> pattern.并使用IQueryable<>模式跟随数据库。

2) In the client repository set the getter on the ConsumerDocuments property to fetch the data. 2) 在客户端存储库中,在ConsumerDocuments属性上设置 getter 以获取数据。

private IEnumerable<ConsumerDocuments> _consumerDocuments;

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

The way I see it, the ViewModel needs to be aware if there's data available or not.在我看来,ViewModel 需要知道是否有可用的数据。 You can hide or disable UI elements that are meaningless without data while the data is being fetched, then show them when the data arrives.您可以在获取数据时隐藏或禁用没有数据就没有意义的 UI 元素,然后在数据到达时显示它们。

You detect that you need to load in some data, so you set the UI to "waiting" mode, kick off the async fetch, then when the data comes in take it out of waiting mode.您检测到需要加载一些数据,因此将 UI 设置为“等待”模式,启动异步获取,然后当数据进入时将其退出等待模式。 Perhaps by having the ViewModel subscribe to a "LoadCompleted" event on the object it's interested in.也许通过让 ViewModel 订阅它感兴趣的 object 上的“LoadCompleted”事件。

(edit) You can avoid excessive loads or circular dependencies by keeping track of the state of each model object: Unloaded/Loading/Loaded. (编辑)您可以通过跟踪每个 model object: Unloaded/Loading/Loaded 的 state 来避免过度负载或循环依赖。

Here is an option E for you.这是给你的选项E。

Asynchronously load data.异步加载数据。 Have the initial fetch queue things up in aa background thread that fills out the full objects slowly.让初始获取队列在后台线程中排队,该线程缓慢地填充完整对象。 And make any methods that require data be loaded behind the scenes be blocking on the load finishing.并使任何需要在后台加载数据的方法在加载完成时被阻塞。 (Blocking and have them notify the background thread that the data that they need is high priority, get them next, so you can unblock ASAP.) (阻塞让他们通知后台线程他们需要的数据是高优先级的,接下来获取它们,这样您就可以尽快解除阻塞。)

This gives you a UI that is immediately responsive when it can be, the ability to write your code and not think about what has been loaded, and it will mostly just work.这为您提供了一个在可能时立即响应的 UI,编写代码而不考虑已加载的内容的能力,并且它大部分都可以正常工作。 The one gotcha is that occasionally you'll make a blocking call while data is loading, however hopefully it won't do that too often.一个问题是,有时您会在数据加载时进行阻塞调用,但希望它不会经常这样做。 If you do, then in the worst case you degrade to something like option C where you have both a blocking fetch of data, and the ability to poll to see if it is there.如果你这样做了,那么在最坏的情况下,你会降级到类似于选项 C 的选项,在该选项中,你既可以阻塞获取数据,又可以轮询以查看它是否存在。 However most of the time you wouldn't have to worry too much about it.但是大多数时候你不必担心太多。

Disclaimer: I personally don't use Windows, and spend most of my time working on back ends far from UIs.免责声明:我个人不使用 Windows,大部分时间都在远离 UI 的后端工作。 If you like the idea, feel free to try it.如果您喜欢这个想法,请随时尝试。 But I haven't actually followed this strategy for anything more complicated than some behind the scenes AJAX calls in a dynamic web page.但我实际上并没有遵循这个策略来处理比在动态 web 页面中调用的幕后 AJAX 更复杂的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM