Silverlight 4 + WCF RIA - 数据服务设计最佳实践

发布于 2024-09-05 12:52:26 字数 5438 浏览 2 评论 0原文

嘿大家。我意识到这是一个相当长的问题,但我真的很感谢任何有 RIA 服务经验的人提供的帮助。谢谢!

我正在开发一个 Silverlight 4 应用程序,用于查看服务器中的数据。我对 RIA 服务相对缺乏经验,因此一直在完成将我需要的数据传递给客户端的任务,但我添加到难题中的每一个新部分似乎都越来越有问题。我觉得我在这里遗漏了一些基本概念,而且似乎我只是以耗时的方式“破解”一些部分,当我尝试添加它们时,每个概念都会破坏之前的概念。我很想获得具有 RIA 服务经验的开发人员的反馈,以找出完成我想做的事情的预期方法。让我列出我正在尝试做的事情:

首先,数据。该数据的来源有多种,主要由共享库创建,该共享库从我们的数据库读取数据,并将其公开为 POCO(普通旧 CLR 对象)。我正在创建自己的 POCO 来表示我需要在服务器和客户端之间传递的不同类型的数据。

DataA - 此应用程序用于近乎实时地查看某种类型的数据,我们可以调用 DataA。每 3 分钟,客户端应该从服务器上拉取自上次请求数据以来所有新 DataA 的数据。

DataB - 用户可以在应用程序中查看 DataA 对象,并可以从列表中选择其中一个,该列表会显示有关该 DataA 的其他详细信息。我将这些额外的详细信息作为 DataB 从服务器上下载下来。

DataC - DataB 包含的内容之一是几个重要值随时间的历史记录。我将此历史记录的每个数据点称为 DataC 对象,每个 DataB 对象包含许多 DataC。

数据模型 - 在服务器端,我有一个 DomainService:

[EnableClientAccess]
public class MyDomainService : DomainService
{
    public IEnumerable<DataA> GetDataA(DateTime? startDate)
    {
        /*Pieces together the DataAs that have been created 
        since startDate, and returns them*/
    }

    public DataB GetDataB(int dataAID)
    {
        /*Looks up the extended info for that dataAID, 
        constructs a new DataB with that DataA's data, 
        plus the extended info (with multiple DataCs in a 
        List<DataC> property on the DataB), and returns it*/
    }

    //Not exactly sure why these are here, but I think it 
    //wouldn't compile without them for some reason? The data 
    //is entirely read-only, so I don't need to update.
    public void UpdateDataA(DataA dataA)
    {
        throw new NotSupportedException();
    }
    public void UpdateDataB(DataB dataB)
    {
        throw new NotSupportedException();
    }
}

DataA/B/C 的类如下所示:

[KnownType(typeof(DataB))]
public partial class DataA
{
    [Key]
    [DataMember]
    public int DataAID { get; set; }
    [DataMember]
    public decimal MyDecimalA { get; set; }
    [DataMember]
    public string MyStringA { get; set; }
    [DataMember]
    public DataTime MyDateTimeA { get; set; }
}

public partial class DataB : DataA
{
    [Key]
    [DataMember]
    public int DataAID { get; set; }
    [DataMember]
    public decimal MyDecimalB { get; set; }
    [DataMember]
    public string MyStringB { get; set; }
    [Include] //I don't know which of these, if any, I need?
    [Composition]
    [Association("DataAToC","DataAID","DataAID")]
    public List<DataC> DataCs { get; set; }
}

public partial class DataC
{
    [Key]
    [DataMember]
    public int DataAID { get; set; }
    [Key]
    [DataMember]
    public DateTime Timestamp { get; set; }
    [DataMember]
    public decimal MyHistoricDecimal { get; set; }
}

我想我在这里遇到的一个大问题是......我应该使用实体而不是 POCO?我的类是否正确构建以能够正确传递数据?我应该在 DomainService 上使用 Invoke 方法而不是 Query (Get) 方法吗?

在客户端方面,我遇到了很多问题。令人惊讶的是,我最大的事情之一就是线程。我没想到 MyDomainContext 会有这么多线程问题。我了解到的是,您似乎只能在 UI 线程上创建 MyDomainContextObjects,您可以进行的所有查询都只能异步完成,并且如果您尝试通过阻塞调用线程来假冒同步执行,直到LoadOperation 完成后,您必须在后台线程上执行此操作,因为它使用 UI 线程来进行查询。这就是我到目前为止所得到的。

应用程序应显示 DataA 对象流,将其中的每 3 分钟块分散到接下来的 3 分钟内(因此它们最终会在发生后 3 分钟显示,看起来像一个连续的流,但只需在 3 分钟内突发下载)。为此,主窗体进行初始化,创建一个私有 MyDomainContext,并启动一个后台工作程序,该工作程序在 while(true) 中连续循环。在每个循环中,它都会检查是否还有剩余的 DataAs 需要显示。如果是这样,它会显示该 Data,并执行 Thread.Sleep(),直到计划显示下一个 DataA。如果它没有数据,它会使用以下方法查询更多数据:

public DataA[] GetDataAs(DateTime? startDate)
{
    _loadOperationGetDataACompletion = new AutoResetEvent(false);
    LoadOperation<DataA> loadOperationGetDataA = null;

    loadOperationGetDataA = 
        _context.Load(_context.GetDataAQuery(startDate),
        System.ServiceModel.DomainServices.Client.LoadBehavior.RefreshCurrent, false);
    loadOperationGetDataA.Completed += new
        EventHandler(loadOperationGetDataA_Completed);

    _loadOperationGetDataACompletion.WaitOne();
    List<DataA> dataAs = new List<DataA>();
    foreach (var dataA in loadOperationGetDataA.Entities)
        dataAs.Add(dataA);

    return dataAs.ToArray();
}

private static AutoResetEvent _loadOperationGetDataACompletion;
private static void loadOperationGetDataA_Completed(object sender, EventArgs e)
{
    _loadOperationGetDataACompletion.Set();
}

试图强制它同步似乎有点笨拙,但由于这已经在后台线程上,我认为这可以吗?到目前为止,一切都确实有效,尽管看起来像是黑客攻击。需要注意的是,如果我尝试在 UI 线程上运行该代码,它会锁定,因为它会永远等待 WaitOne(),锁定线程,因此它无法向服务器发出 Load 请求。

因此,一旦显示数据,用户可以在数据经过时单击其中一个,以使用有关该对象的完整 DataB 数据填充详细信息窗格。为此,我让详细信息窗格用户控件订阅我设置的选择事件,当选择更改时(在 UI 线程上)会触发该事件。我在那里使用类似的技术来获取 DataB 对象:

void SelectionService_SelectedDataAChanged(object sender, EventArgs e)
{
    DataA dataA = /*Get the selected DataA*/;

    MyDomainContext context = new MyDomainContext();
    var loadOperationGetDataB = 
        context.Load(context.GetDataBQuery(dataA.DataAID),
        System.ServiceModel.DomainServices.Client.LoadBehavior.RefreshCurrent, false);
    loadOperationGetDataB.Completed += new
        EventHandler(loadOperationGetDataB_Completed);          
}

private void loadOperationGetDataB_Completed(object sender, EventArgs e)
{
    this.DataContext = 
        ((LoadOperation<DataB>)sender).Entities.SingleOrDefault();  
}

同样,这似乎有点 hacky,但它有效......除了它加载的 DataB 之外,DataCs 列表是空的。我在那里尝试了各种方法,但我不明白我做错了什么,让 DataC 与 DataB 一起下来。我正准备对 DataC 进行第三次查询,但这对我来说更加骇人听闻。

真的感觉就像我正在与这里的谷物作斗争,就像我以一种完全无意识的方式这样做一样。如果有人可以提供任何帮助,并指出我在这里做错了什么,我将非常感激!

谢谢!

Hey all. I realize this is a rather long question, but I'd really appreciate any help from anyone experienced with RIA services. Thanks!

I'm working on a Silverlight 4 app that views data from the server. I'm relatively inexperienced with RIA Services, so have been working through the tasks of getting the data I need down to the client, but every new piece I add to the puzzle seems to be more and more problematic. I feel like I'm missing some basic concepts here, and it seems like I'm just 'hacking' pieces on, in time-consuming ways, each one breaking the previous ones as I try to add them. I'd love to get the feedback of developers experienced with RIA services, to figure out the intended way to do what I'm trying to do. Let me lay out what I'm trying to do:

First, the data. The source of this data is a variety of sources, primarily created by a shared library which reads data from our database, and exposes it as POCOs (Plain Old CLR Objects). I'm creating my own POCOs to represent the different types of data I need to pass between server and client.

DataA - This app is for viewing a certain type of data, lets call DataA, in near-realtime. Every 3 minutes, the client should pull data down from the server, of all the new DataA since the last time it requested data.

DataB - Users can view the DataA objects in the app, and may select one of them from the list, which displays additional details about that DataA. I'm bringing these extra details down from the server as DataB.

DataC - One of the things that DataB contains is a history of a couple important values over time. I'm calling each data point of this history a DataC object, and each DataB object contains many DataCs.

The Data Model - On the server side, I have a single DomainService:

[EnableClientAccess]
public class MyDomainService : DomainService
{
    public IEnumerable<DataA> GetDataA(DateTime? startDate)
    {
        /*Pieces together the DataAs that have been created 
        since startDate, and returns them*/
    }

    public DataB GetDataB(int dataAID)
    {
        /*Looks up the extended info for that dataAID, 
        constructs a new DataB with that DataA's data, 
        plus the extended info (with multiple DataCs in a 
        List<DataC> property on the DataB), and returns it*/
    }

    //Not exactly sure why these are here, but I think it 
    //wouldn't compile without them for some reason? The data 
    //is entirely read-only, so I don't need to update.
    public void UpdateDataA(DataA dataA)
    {
        throw new NotSupportedException();
    }
    public void UpdateDataB(DataB dataB)
    {
        throw new NotSupportedException();
    }
}

The classes for DataA/B/C look like this:

[KnownType(typeof(DataB))]
public partial class DataA
{
    [Key]
    [DataMember]
    public int DataAID { get; set; }
    [DataMember]
    public decimal MyDecimalA { get; set; }
    [DataMember]
    public string MyStringA { get; set; }
    [DataMember]
    public DataTime MyDateTimeA { get; set; }
}

public partial class DataB : DataA
{
    [Key]
    [DataMember]
    public int DataAID { get; set; }
    [DataMember]
    public decimal MyDecimalB { get; set; }
    [DataMember]
    public string MyStringB { get; set; }
    [Include] //I don't know which of these, if any, I need?
    [Composition]
    [Association("DataAToC","DataAID","DataAID")]
    public List<DataC> DataCs { get; set; }
}

public partial class DataC
{
    [Key]
    [DataMember]
    public int DataAID { get; set; }
    [Key]
    [DataMember]
    public DateTime Timestamp { get; set; }
    [DataMember]
    public decimal MyHistoricDecimal { get; set; }
}

I guess a big question I have here is... Should I be using Entities instead of POCOs? Are my classes constructed correctly to be able to pass the data down correctly? Should I be using Invoke methods instead of Query (Get) methods on the DomainService?

On the client side, I'm having a number of issues. Surprisingly, one of my biggest ones has been threading. I didn't expect there to be so many threading issues with MyDomainContext. What I've learned is that you only seem to be able to create MyDomainContextObjects on the UI thread, all of the queries you can make are done asynchronously only, and that if you try to fake doing it synchronously by blocking the calling thread until the LoadOperation finishes, you have to do so on a background thread, since it uses the UI thread to make the query. So here's what I've got so far.

The app should display a stream of the DataA objects, spreading each 3min chunk of them over the next 3min (so they end up displayed 3min after the occurred, looking like a continuous stream, but only have to be downloaded in 3min bursts). To do this, the main form initializes, creates a private MyDomainContext, and starts up a background worker, which continuously loops in a while(true). On each loop, it checks if it has any DataAs left over to display. If so, it displays that Data, and Thread.Sleep()s until the next DataA is scheduled to be displayed. If it's out of data, it queries for more, using the following methods:

public DataA[] GetDataAs(DateTime? startDate)
{
    _loadOperationGetDataACompletion = new AutoResetEvent(false);
    LoadOperation<DataA> loadOperationGetDataA = null;

    loadOperationGetDataA = 
        _context.Load(_context.GetDataAQuery(startDate),
        System.ServiceModel.DomainServices.Client.LoadBehavior.RefreshCurrent, false);
    loadOperationGetDataA.Completed += new
        EventHandler(loadOperationGetDataA_Completed);

    _loadOperationGetDataACompletion.WaitOne();
    List<DataA> dataAs = new List<DataA>();
    foreach (var dataA in loadOperationGetDataA.Entities)
        dataAs.Add(dataA);

    return dataAs.ToArray();
}

private static AutoResetEvent _loadOperationGetDataACompletion;
private static void loadOperationGetDataA_Completed(object sender, EventArgs e)
{
    _loadOperationGetDataACompletion.Set();
}

Seems kind of clunky trying to force it into being synchronous, but since this already is on a background thread, I think this is OK? So far, everything actually works, as much of a hack as it seems like it may be. It's important to note that if I try to run that code on the UI thread, it locks, because it waits on the WaitOne() forever, locking the thread, so it can't make the Load request to the server.

So once the data is displayed, users can click on one as it goes by to fill a details pane with the full DataB data about that object. To do that, I have the the details pane user control subscribing to a selection event I have setup, which gets fired when the selection changes (on the UI thread). I use a similar technique there, to get the DataB object:

void SelectionService_SelectedDataAChanged(object sender, EventArgs e)
{
    DataA dataA = /*Get the selected DataA*/;

    MyDomainContext context = new MyDomainContext();
    var loadOperationGetDataB = 
        context.Load(context.GetDataBQuery(dataA.DataAID),
        System.ServiceModel.DomainServices.Client.LoadBehavior.RefreshCurrent, false);
    loadOperationGetDataB.Completed += new
        EventHandler(loadOperationGetDataB_Completed);          
}

private void loadOperationGetDataB_Completed(object sender, EventArgs e)
{
    this.DataContext = 
        ((LoadOperation<DataB>)sender).Entities.SingleOrDefault();  
}

Again, it seems kinda hacky, but it works... except on the DataB that it loads, the DataCs list is empty. I've tried all kinds of things there, and I don't see what I'm doing wrong to allow the DataCs to come down with the DataB. I'm about ready to make a 3rd query for the DataCs, but that's screaming even more hackiness to me.

It really feels like I'm fighting against the grain here, like I'm doing this in an entirely unintended way. If anyone could offer any assistance, and point out what I'm doing wrong here, I'd very much appreciate it!

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

゛清羽墨安 2024-09-12 12:52:26

我不得不说它看起来确实有点过于复杂。

如果您使用实体框架(如果需要,版本 4 可以生成 POCO)和 Ria /LINQ 您可以使用延迟加载、“扩展”语句和每个类型继承的表来隐式完成所有这些操作。

I have to say it does seem a bit overly complex.

If you use the entity framework (which with version 4 can generate POCO's if you need) and Ria /LINQ You can do all of that implicitly using lazy loading, 'expand' statements and table per type inheritance.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文