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

时间:2010-06-11 23:39:42

标签: c# .net silverlight wcf-ria-services

嘿所有人。我意识到这是一个相当长的问题,但我非常感谢任何有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,并启动一个后台工作程序,它会连续循环一段时间(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列表是空的。我在那里尝试了各种各样的东西,但是我没有看到我做错了什么让DataCs能够使用DataB。我准备好为DataCs做第三次查询了,但这对我来说更加尖叫。

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

谢谢!

1 个答案:

答案 0 :(得分:0)

我不得不说这看起来有点过于复杂。

如果你使用实体框架(版本4可以根据需要生成POCO)和Ria / LINQ你可以使用延迟加载隐式地完成所有这些操作,'expand'语句和每个类型继承的表。