Silverlight、DataPager、RIA 服务和智能分页

发布于 2024-08-21 14:59:51 字数 497 浏览 4 评论 0原文

我仍然在努力尝试使用 Silverlight 和 RIA 服务,当然首先是从一些更“有趣”的东西开始,比如网格和智能分页。我可以连接到 RIA 服务(使用自行开发的 ORM,而不是 L2S 或 EF),获取网格上的数据,并连接到 DataPager。域服务与本地 ORM 配合良好,至少在查询方面如此。 (仍在进行完整的 CRUD 工作。)但是,仍然存在问题:

  1. 为了支持用户应用程序,除了智能分页之外,我还需要用户控制的排序和过滤(仅对需要显示的行运行查询)和分组。

  2. 到目前为止,我还没有在 DataGrid 或 DataPager 中看到任何可以外部化这些功能的内容,以便可以将过滤、排序和分页参数传递到服务器以构建适当的查询。

  3. 数据集可能非常大;我为原型设计工作选择的表在某些客户处可以容纳多达 35,000 个条目,并且我确信还有其他更大的表我在某个时候必须处理。因此“智能分页”方面至关重要。

欢迎提出想法、建议、指导和削弱砖块。

I'm still trying to get my feet on the ground with Silverlight and RIA Services, and of course starting with some of the more "fun" stuff like grids and intelligent paging. I can connect to RIA Services (using a home-grown ORM, not L2S or EF), get data on the grid, and connect to a DataPager. The domain service is working well with the home-grown ORM, at least for queries. (Still working on full CRUD.) However, there are still problems:

  1. To support the user application, I need user-controlled sorting and filtering, in addition to smart paging (only run the query for the rows needed to display) and grouping.

  2. So far, I've seen nothing in the DataGrid or DataPager to externalize these capabilities so that filtering, sorting, and paging parameters can be passed to the server to build the appropriate query.

  3. The datasets are potentially quite large; my table I've chosen for prototyping work can have up to 35,000 entries at some customers, and I'm sure there are other tables far larger that I will have to deal with at some point. So the "smart paging" aspect is essential.

Ideas, suggestions, guidance, and nerf bricks are all welcome.

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

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

发布评论

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

评论(3

橘虞初梦 2024-08-28 14:59:51

好吧,我已经在杂草中度过了几天,我想我已经掌握了它。

首先,一个重要的魔法。为了使分页正常工作,分页器必须知道总项目数,无论当前查询返回了多少项目。如果查询返回所有内容,则项目计数显然是返回的项目数。对于智能分页,项目计数仍然是可用项目的总数,尽管查询仅返回显示的内容。通过过滤,即使每次过滤器更改时,可用项目的总数也会发生变化。

Silverlight Datapager 控件有一个名为 ItemCount 的属性。它是只读的,不能在 XAML 中进行数据绑定,也不能直接在代码中设置。但是,如果包含分页器的用户控件具有实现 IPgedCollectionView 的 DataContext,则数据上下文对象必须实现具有 PropertyChanged 通知的 ItemCount 属性,并且 DataPager 似乎会自动选取此属性。

其次,我强烈推荐 Brad Abrams 的优秀 有关 RIA 服务的一系列博客文章,尤其是 ViewModel。它包含了进行分页和过滤工作所需的大部分内容,尽管它缺少管理项目计数的关键部分。他的可下载示例还包含用于实现 ModelViewViewModel (MVVM) 的非常好的基本框架。谢谢你,布拉德!

以下是如何使项目计数发挥作用。 (此代码指的是自定义 ORM,而 Brad 的代码使用实体框架;在两者之间,您可以计算出您的环境中需要什么。)

首先,您的 ORM 需要支持获取记录计数,无论是否有过滤器。下面是我的域服务代码,它使计数可用于 RIA 服务:

[Invoke]
public int GetExamCount()
{
    return Context.Exams.Count();
}

[Invoke]
public int GetFilteredExamCount(string descriptionFilter)
{
    return Context.Exams.GetFilteredCount(descriptionFilter);
}

请注意 [Invoke] 属性。对于任何不返回实体或实体集合的 DomainService 方法,您都需要它。

现在是 ViewModel 代码。当然,您需要一个 ItemCount。 (这是来自 Brad 的示例。)

    int itemCount;
    public int ItemCount
    {
        get { return itemCount; }
        set
        {
            if (itemCount != value)
            {
                itemCount = value;
                RaisePropertyChanged(ItemCountChangedEventArgs);
            }
        }
    }

您的 LoadData 方法将运行查询以获取要在 DataGrid 中显示的当前行集。 (这还没有实现自定义排序,但这是一个简单的添加。)

    EntityQuery<ExamEntity> query = 
        DomainContext.GetPagedExamsQuery(PageSize * PageIndex, PageSize, DescriptionFilterText);
    DomainContext.Load(query, OnExamsLoaded, null);

然后回调方法运行查询来获取计数。如果没有使用过滤器,我们将获得所有行的计数;如果有过滤器,那么我们会得到过滤后的行数。

private void OnExamsLoaded(LoadOperation<ExamEntity> loadOperation)
{
    if (loadOperation.Error != null)
    {
        //raise an event... 
        ErrorRaising(this, new ErrorEventArgs(loadOperation.Error));
    }
    else
    {
        Exams.MoveCurrentToFirst();
        if (string.IsNullOrEmpty(DescriptionFilterText))
        {
            DomainContext.GetExamCount(OnCountCompleted, null);
        }
        else
        {
            DomainContext.GetFilteredExamCount(DescriptionFilterText, OnCountCompleted, null);
        }
        IsLoading = false;
    }
}

还有一个用于计数的回调方法:

void OnCountCompleted(InvokeOperation<int> op)
{
    ItemCount = op.Value;
    TotalItemCount = op.Value;
}

设置了 ItemCount 后,Datapager 控件将拾取它,并且我们可以进行带过滤的分页和仅返回要显示的记录的智能查询!

LINQ 通过 .Skip() 和 .Take() 使查询变得简单。使用原始 ADO.NET 执行此操作比较困难。我通过分解 LINQ 生成的查询来了解如何做到这一点。

SELECT * FROM 
    (select ROW_NUMBER() OVER (ORDER BY Description) as rownum, * 
     FROM Exams as T0  WHERE T0.Description LIKE @description ) as T1 
WHERE T1.rownum between @first AND @last ORDER BY rownum

子句“select ROW_NUMBER() OVER (ORDER BY Description) as rownum”是有趣的部分,因为还没有多少人使用“OVER”。该子句在分配行号之前对表进行“描述”排序,并且在分配行号之前也会应用过滤器。这允许外部 SELECT 在排序和过滤后根据行号进行过滤。

这就是 RIA 服务和 Silverlight 中具有过滤功能的智能分页!

OK, I've spent a few days in the weeds with this one, and I think I've got a handle on it.

First, an important piece of magic. For paging to work properly, the pager has to know the total item count, no matter how many items were returned by the current query. If the query returns everything, the item count is obviously the number of items returned. For smart paging, the item count is still the total of available items, although the query returns only what gets displayed. With filtering, even the total of available items changes every time the filter changes.

The Silverlight Datapager control has a property called ItemCount. It is readonly and cannot be databound in XAML, or set directly in code. However, if the user control containing the pager has a DataContext that implements IPagedCollectionView, then the data context object must implement an ItemCount property with PropertyChanged notification, and the DataPager seems to pick this up automagically.

Second, I highly recommend Brad Abrams' excellent series of blog posts on RIA Services, especially this one on ViewModel. It contains most of what you need to make paging and filtering work, although it's missing the critical piece on managing the item count. His downloadable sample also contains a very good basic framework for implementing ModelViewViewModel (MVVM). Thank you, Brad!

So here's how to make the item count work. (This code refers to a custom ORM, while Brad's code uses Entity Framework; between the two you can figure you what you need in your environment.)

First, your ORM needs to support getting record counts, with and without your filter. Here's my domain service code that makes the counts available to RIA Services:

[Invoke]
public int GetExamCount()
{
    return Context.Exams.Count();
}

[Invoke]
public int GetFilteredExamCount(string descriptionFilter)
{
    return Context.Exams.GetFilteredCount(descriptionFilter);
}

Note the [Invoke] attribute. You need this for any DomainService method that doesn't return an Entity or an Entity collection.

Now for the ViewModel code. You need an ItemCount, of course. (This is from Brad's example.)

    int itemCount;
    public int ItemCount
    {
        get { return itemCount; }
        set
        {
            if (itemCount != value)
            {
                itemCount = value;
                RaisePropertyChanged(ItemCountChangedEventArgs);
            }
        }
    }

Your LoadData method will run the query to get the current set of rows for display in the DataGrid. (This doesn't implement custom sorting yet, but that's an easy addition.)

    EntityQuery<ExamEntity> query = 
        DomainContext.GetPagedExamsQuery(PageSize * PageIndex, PageSize, DescriptionFilterText);
    DomainContext.Load(query, OnExamsLoaded, null);

The callback method then runs the query to get the counts. If no filter is being used, we get the count for all rows; if there's a filter, then we get the count for filtered rows.

private void OnExamsLoaded(LoadOperation<ExamEntity> loadOperation)
{
    if (loadOperation.Error != null)
    {
        //raise an event... 
        ErrorRaising(this, new ErrorEventArgs(loadOperation.Error));
    }
    else
    {
        Exams.MoveCurrentToFirst();
        if (string.IsNullOrEmpty(DescriptionFilterText))
        {
            DomainContext.GetExamCount(OnCountCompleted, null);
        }
        else
        {
            DomainContext.GetFilteredExamCount(DescriptionFilterText, OnCountCompleted, null);
        }
        IsLoading = false;
    }
}

There's also a callback method for counts:

void OnCountCompleted(InvokeOperation<int> op)
{
    ItemCount = op.Value;
    TotalItemCount = op.Value;
}

With the ItemCount set, the Datapager control picks it up, and we have paging with filtering and a smart query that returns only the records to be displayed!

LINQ makes the query easy with .Skip() and .Take(). Doing this with raw ADO.NET is harder. I learned how to do this by taking apart a LINQ-generated query.

SELECT * FROM 
    (select ROW_NUMBER() OVER (ORDER BY Description) as rownum, * 
     FROM Exams as T0  WHERE T0.Description LIKE @description ) as T1 
WHERE T1.rownum between @first AND @last ORDER BY rownum

The clause "select ROW_NUMBER() OVER (ORDER BY Description) as rownum" is the interesting part, because not many people use "OVER" yet. This clause sorts the table on Description before assigning row numbers, and the filter is also applied before row numbers are assigned. This allows the outer SELECT to filter on row numbers, after sorting and filtering.

So there it is, smart paging with filtering, in RIA Services and Silverlight!

小兔几 2024-08-28 14:59:51

这是快速但肮脏的解决方案(我寻求的):

只需将您的 DomainDataSource 移动到您的 ViewModel 即可!完毕!

对于可测试性来说可能不太好,可能还有一些我还没有发现的其他限制,但我个人并不关心这个直到 更好的东西出现了

在 ViewModel 中只需实例化数据源:

// Feedback DataSource
_dsFeedback = new DomainDataSource();
_dsFeedback.DomainContext = _razorSiteDomainContext;
_dsFeedback.QueryName = "GetOrderedFeedbacks";
_dsFeedback.PageSize = 10;
_dsFeedback.Load();

并提供可绑定属性:

private DomainDataSource _dsFeedback { get; set; }
public DomainDataSource Feedback 
{
    get 
    {
        return _dsFeedback;
    }
}

并将 DataPager 添加到 XAML:

  <data:DataPager Grid.Row="1"
                  HorizontalAlignment="Stretch" 
                  Source="{Binding Feedback.Data}" 
                  Margin="0,0,0,5" />

  <data:DataGrid ItemsSource="{Binding Feedback.Data}">

PS。感谢上面链接页面中的“Francois”。在看到您的评论之前,我什至没有意识到我可以将 DomainDataSource 从 XAML 中取出!

Here's the quick and dirty solution (that I went for):

Just move your DomainDataSource to your ViewModel! Done!

May not exactly be great for testability and probably some other limitations I haven't discovered yet, but personally I don't care about that until something better comes along.

Inside your ViewModel just instantiate the data source :

// Feedback DataSource
_dsFeedback = new DomainDataSource();
_dsFeedback.DomainContext = _razorSiteDomainContext;
_dsFeedback.QueryName = "GetOrderedFeedbacks";
_dsFeedback.PageSize = 10;
_dsFeedback.Load();

and provide a bindable property :

private DomainDataSource _dsFeedback { get; set; }
public DomainDataSource Feedback 
{
    get 
    {
        return _dsFeedback;
    }
}

And add your DataPager to your XAML:

  <data:DataPager Grid.Row="1"
                  HorizontalAlignment="Stretch" 
                  Source="{Binding Feedback.Data}" 
                  Margin="0,0,0,5" />

  <data:DataGrid ItemsSource="{Binding Feedback.Data}">

PS. Thanks to 'Francois' from the above linked page. I didn't even realize I could take the DomainDataSource out of the XAML until I saw your comment!

中性美 2024-08-28 14:59:51

这是 2010 年 5 月发表的一篇有趣的文章,介绍了框架中未来可能支持此类功能的情况。

http://www.riaservicesblog.net/Blog/帖子/WCF-RIA-Services-Speculation-EntityCollectionView.aspx

This is an interesting article from May 2010 about the possible future support for this type of feature in the framework.

http://www.riaservicesblog.net/Blog/post/WCF-RIA-Services-Speculation-EntityCollectionView.aspx

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