设置某些实体页面大小时,WCF 数据服务展开行为异常
在为生产准备 WCF 数据服务时,我们遇到了启用分页时展开运算符的行为问题。
禁用分页后,展开将按预期工作。但是,当我在任何扩展实体集上启用分页时,无论页面大小如何,扩展实体都会以大小为 1 进行分页。
[更新]
在没有任何进一步输入的情况下在这里或 MSDN 论坛我创建了 Connect 上的错误。也许翻墙的人会水落石出!
例如,假设我有以下简单模型:
它在生成的 SQL 数据库上运行,并包含一些示例数据:
INSERT INTO [dbo].[Towns] (Name) VALUES ('Berlin');
INSERT INTO [dbo].[Towns] (Name) VALUES ('Rome');
INSERT INTO [dbo].[Towns] (Name) VALUES ('Paris');
INSERT INTO [dbo].[Gentlemen] (Id, Name) VALUES (1, 'Johnny');
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Frieda', 'Berlin', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Adelita', 'Berlin', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Milla', 'Berlin', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Georgine', 'Paris', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Nannette', 'Paris', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Verona', 'Rome', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Gavriella', 'Rome', 1);
数据服务很简单(请注意,此处分页已禁用):
namespace TestWCFDataService
{
public class TestWCFDataService : DataService<TestModel.TestModelContainer>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Ladies", EntitySetRights.AllRead);
config.SetEntitySetAccessRule("Gentlemen", EntitySetRights.AllRead);
config.SetEntitySetAccessRule("Towns", EntitySetRights.AllRead);
//config.SetEntitySetPageSize("Ladies", 10);
//config.SetEntitySetPageSize("Gentlemen", 10);
//config.SetEntitySetPageSize("Towns", 10);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
}
现在,我的用户想要找到 Town
为“Berlin”的每一位 Lady
以及他们的 Gentleman
是谁。
有问题的查询是:
http://localhost:62946/TestWCFDataService.svc/Towns('Berlin')?$expand=Ladies/Gentleman
当我运行此查询(JSON,因为 Atom 版本很大)时,我得到了预期的输出;小镇上有三位女士,她们的绅士都是约翰尼。
var result = {
"d": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Towns('Berlin')", "type": "TestModel.Town"
}, "Name": "Berlin", "Ladies": [
{
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(1)", "type": "TestModel.Lady"
}, "Id": 1, "Name": "Frieda", "Gentleman": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)", "type": "TestModel.Gentleman"
}, "Id": 1, "Name": "Johnny", "Ladies": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)/Ladies"
}
}
}, "Town": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(1)/Town"
}
}
}, {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(2)", "type": "TestModel.Lady"
}, "Id": 2, "Name": "Adelita", "Gentleman": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)", "type": "TestModel.Gentleman"
}, "Id": 1, "Name": "Johnny", "Ladies": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)/Ladies"
}
}
}, "Town": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(2)/Town"
}
}
}, {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(3)", "type": "TestModel.Lady"
}, "Id": 3, "Name": "Milla", "Gentleman": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)", "type": "TestModel.Gentleman"
}, "Id": 1, "Name": "Johnny", "Ladies": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)/Ladies"
}
}
}, "Town": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(3)/Town"
}
}
}
]
}
}
最终将会有很多城镇
,所以我启用了城镇寻呼。
...
config.SetEntitySetPageSize("Towns", 10);
...
查询继续按预期运行。但也会有很多Ladies
和Gentlemen
,所以我希望能够限制返回的结果数量:
...
config.SetEntitySetPageSize("Ladies", 10);
config.SetEntitySetPageSize("Gentlemen", 10);
...
但是当我设置页面大小时在女士实体集或绅士实体集(或两者)上,我的查询结果意外更改:
var result = {
"d": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Towns('Berlin')", "type": "TestModel.Town"
}, "Name": "Berlin", "Ladies": {
"results": [
{
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(1)", "type": "TestModel.Lady"
}, "Id": 1, "Name": "Frieda", "Gentleman": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)", "type": "TestModel.Gentleman"
}, "Id": 1, "Name": "Johnny", "Ladies": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)/Ladies"
}
}
}, "Town": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(1)/Town"
}
}
}
]
}
}
}
扩展仅包括女士对象之一(尽管至少包括她的绅士)。无论页面大小设置为多大,查询仍然只返回扩展集合中的一个对象。
是否在一个或两个扩展实体上设置页面大小也并不重要,只要其中一个实体设置了页面大小,那么只有一个 Lady
对象会被急切已加载。
根据 OData 规范,这种行为对我来说有问题:
“带有 $expand 系统查询选项的 URI 表示与 URI 的资源路径部分标识的条目或条目集合关联的条目必须内联表示(即急切加载)。”
我是否误读了规范?我应该预料到这种行为吗?我只是希望能够在直接访问时限制实体集的页面大小,但也让它们可以立即加载。
这是 WCF 数据服务中的错误吗? (或者我的代码?或者我的大脑?)
[编辑]
更多信息:WCF 数据服务文档 指出:
“此外,当在数据服务中启用分页时,您必须从服务中显式加载后续数据页。”
但我找不到解释为什么无论指定什么页面大小,相关实体集的页面大小似乎默认为 1。
[编辑]
更多信息:有问题的版本是 .NET 4 版本 4.0.30319
和 System.Data.Services
版本 4.0.0.0
。该版本随 Visual Studio 2010(安装了 SP1)一起提供。
[编辑]
显示该行为的示例解决方案现已在 github 存储库。它在 InitializeService
方法和数据库创建脚本中打开了分页,该脚本还添加了一些示例数据,以便我们位于同一页面上。
While getting our WCF Data Service ready for production we encountered an issue with the behaviour of the expand operator when paging is enabled.
With paging disabled, expand works as expected. But when I enable paging on any of the expanded entity sets, no matter what the page sizes, the expanded entities appear to page with a size of 1.
[UPDATE]
In the absence of any further input from here or the MSDN forums I've created a bug on Connect. Maybe someone over the wall will get to the bottom of it!
For example, supposed I have the following simple model:
It's running on a generated SQL database with some sample data:
INSERT INTO [dbo].[Towns] (Name) VALUES ('Berlin');
INSERT INTO [dbo].[Towns] (Name) VALUES ('Rome');
INSERT INTO [dbo].[Towns] (Name) VALUES ('Paris');
INSERT INTO [dbo].[Gentlemen] (Id, Name) VALUES (1, 'Johnny');
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Frieda', 'Berlin', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Adelita', 'Berlin', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Milla', 'Berlin', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Georgine', 'Paris', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Nannette', 'Paris', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Verona', 'Rome', 1);
INSERT INTO [dbo].[Ladies] (Name, Town_Name, Gentleman_Id) VALUES ('Gavriella', 'Rome', 1);
The Data Service is straightforward (note that here paging is disabled):
namespace TestWCFDataService
{
public class TestWCFDataService : DataService<TestModel.TestModelContainer>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Ladies", EntitySetRights.AllRead);
config.SetEntitySetAccessRule("Gentlemen", EntitySetRights.AllRead);
config.SetEntitySetAccessRule("Towns", EntitySetRights.AllRead);
//config.SetEntitySetPageSize("Ladies", 10);
//config.SetEntitySetPageSize("Gentlemen", 10);
//config.SetEntitySetPageSize("Towns", 10);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
}
Now, my user wants to find every Lady
whose Town
is "Berlin" and also who their Gentleman
is.
The query in question is:
http://localhost:62946/TestWCFDataService.svc/Towns('Berlin')?$expand=Ladies/Gentleman
When I run this query (JSON because the Atom version is gigantic) I get the expected output; a town with three ladies, all of whom have Johnny as their gentleman.
var result = {
"d": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Towns('Berlin')", "type": "TestModel.Town"
}, "Name": "Berlin", "Ladies": [
{
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(1)", "type": "TestModel.Lady"
}, "Id": 1, "Name": "Frieda", "Gentleman": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)", "type": "TestModel.Gentleman"
}, "Id": 1, "Name": "Johnny", "Ladies": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)/Ladies"
}
}
}, "Town": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(1)/Town"
}
}
}, {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(2)", "type": "TestModel.Lady"
}, "Id": 2, "Name": "Adelita", "Gentleman": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)", "type": "TestModel.Gentleman"
}, "Id": 1, "Name": "Johnny", "Ladies": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)/Ladies"
}
}
}, "Town": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(2)/Town"
}
}
}, {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(3)", "type": "TestModel.Lady"
}, "Id": 3, "Name": "Milla", "Gentleman": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)", "type": "TestModel.Gentleman"
}, "Id": 1, "Name": "Johnny", "Ladies": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)/Ladies"
}
}
}, "Town": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(3)/Town"
}
}
}
]
}
}
There are going to be many Towns
eventually so I enable paging for Town.
...
config.SetEntitySetPageSize("Towns", 10);
...
The query continues to function as expected. But there are also going to be a lot of Ladies
and Gentlemen
so I want to be able to limit the number of results that are returned:
...
config.SetEntitySetPageSize("Ladies", 10);
config.SetEntitySetPageSize("Gentlemen", 10);
...
But when I set a page size on either the Ladies entity set or the Gentlemen entity set (or both) the results of my query change unexpectedly:
var result = {
"d": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Towns('Berlin')", "type": "TestModel.Town"
}, "Name": "Berlin", "Ladies": {
"results": [
{
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(1)", "type": "TestModel.Lady"
}, "Id": 1, "Name": "Frieda", "Gentleman": {
"__metadata": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)", "type": "TestModel.Gentleman"
}, "Id": 1, "Name": "Johnny", "Ladies": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Gentlemen(1)/Ladies"
}
}
}, "Town": {
"__deferred": {
"uri": "http://localhost:62946/TestWCFDataService.svc/Ladies(1)/Town"
}
}
}
]
}
}
}
The expand only includes one of the Lady objects (although at least her Gentleman is included). It doesn't matter how large the page size is set to, the query still only returns one object in the expanded collection.
It also does not matter whether or not the page size is set on one or both of the expanded entities, as long as one of them has a page size set then only one of the Lady
objects will be eagerly loaded.
This behaviour smells buggy to me, as according to the OData Specification:
"A URI with a $expand System Query Option indicates that Entries associated with the Entry or Collection of Entries identified by the Resource Path section of the URI must be represented inline (i.e. eagerly loaded)."
Am I misreading the spec? Should I have expected this behaviour? I just want to be able to limit the page size of the entity sets when accessed directly but also have them eagerly loadable.
Is it a bug in WCF Data Services? (or my code? or my brain?)
[EDIT]
More info: the documentation for WCF Data Services states that:
"Also, when paging is enabled in the data service, you must explicitly load subsequent data pages from the service."
But I can't find an explanation of why the page size for the related entity sets seems to default to 1 no matter what page size is specified.
[EDIT]
Yet more info: the version in question is on .NET 4 version 4.0.30319
with System.Data.Services
version 4.0.0.0
. It's the version that comes in the box with Visual Studio 2010 (with SP1 installed).
[EDIT]
A sample solution showing the behaviour is now up in a github repository. It's got paging turned on in the InitializeService
method and a DB creation script that also adds some sample data so that we're on the same page.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
花了几个月的时间,但这显然 将在下一个版本中修复:
It took a few months but this is apparently going to be fixed in the next version:
您使用的是哪个版本的 WCF 数据服务?
我发现了一个与在 .NET Framework 4 中使用 Expand 和服务器驱动分页有关的错误,但我认为它仅影响具有复合键的实体以及使用 OrderBy 选项时,这两个选项似乎都不适用。
不过,这听起来确实像是一个错误。
您是否尝试过使用 Atom 而不是 JSON,如果是,展开中的实体是否仍然丢失?
What version of WCF Data Services are you using?
There is a bug that I found relating to using Expand with server-driven paging in .NET Framework 4, but I thought that it only affected entities with compound keys and when using the OrderBy option, neither of which seem to apply here.
Still it definitely sounds like a bug.
Have you tried using Atom instead of JSON, and if so are the entities in the expand still missing?
您的查询:
本地主机:62946/TestWCFDataService.svc/Towns('Berlin')?$expand=Ladies/Gentleman
不会展开 Ladies,仅展开 Gentelman。
查询应如下所示:
localhost:62946/TestWCFDataService.svc/Towns('Berlin')?$expand=Ladies,Ladies/Gentleman。
希望这有帮助!
莫妮卡·弗林图
Your query:
localhost:62946/TestWCFDataService.svc/Towns('Berlin')?$expand=Ladies/Gentleman
doesn't expand Ladies, only the Gentelman.
The query should look like:
localhost:62946/TestWCFDataService.svc/Towns('Berlin')?$expand=Ladies,Ladies/Gentleman.
Hope this helps!
Monica Frintu