65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Simple.OData.Client 消费 OData Feed 的 12 个理由

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (8投票s)

2013 年 11 月 22 日

CPOL

18分钟阅读

viewsIcon

70368

本文介绍了 WCF Data Services 客户端的局限性,并展示了 Simple.OData.Client 如何成为更好的替代方案。

Mauricio Scheffer 在他关于 NuGet 依赖项监视器的精彩博文中,专门用了一个部分来批评 WCF Data Services。而这种批评是当之无愧的。任何使用 WCF Data Services 客户端处理过比简单演示更复杂的需求的人都会明白 Mauricio 在说什么。我接触 WCF Data Services 大约有三年了,我记得最初发现可以将 LINQ 表达式发送到 WCF Data Services 客户端,客户端将其转换为 OData HTTP 请求时,我有多么高兴。但我也记得后来发现的失望:我无法像使用 SQL 数据库那样编写 LINQ 表达式。我必须仔细思考 LINQ 表达式的语法,然后一遍又一遍地测试。它们都能编译通过,但我从不知道它们是否会在运行时崩溃,无论是客户端还是服务器端。

阅读 Mauricio Scheffer 博文的最后一节,您就会明白 WCF Data Services 的 LINQ 表达式有多么脆弱。如果您想将查询结果投影到单个列,您不能简单地写“x.Id”——您需要写“new { x.Id }”。否定布尔表达式——您将收到一条含糊的错误消息。交换 Where 和 Select 子句——之前工作的语句将不再起作用。所有这些失败都与 OData 协议的限制(例如不支持 JOINs)无关。它们都是 WCF Data Services LINQ 表达式解析器的局限性——主要在客户端,有时在服务器端。

我对 WCF Data Services LINQ 支持的不满如此之大,以至于我甚至编写了自己的 OData 客户端。它最初被打包为 Simple.Data OData 适配器,添加到由Mark Rendle开发的优秀微型 ORM 的适配器集合中。然而,Simple.Data API 是围绕关系数据库设计的,因此忽略了一些不太常见的 OData 场景,此外,我希望我的客户端支持 Mono 和非 Windows 移动平台。因此,我从 Simple.Data OData 适配器中提取了一个平台无关的核心,并将其打包为 Simple.OData.Client(Simple.Data OData 适配器内部使用)。当然,当我读到 Mauricio 的帖子时,我立即被激起了测试 Simple.OData.Client 命令的动力,而这些命令是 WCF Data Services 客户端无法执行的。

既然您正在阅读这篇博文,您可能已经猜到我对结果很满意。Simple.OData.Client 不仅支持 WCF Data Services 中未实现的特性,还提供了语法替代方案,我将进一步介绍其流畅界面的各种变体:类型化和动态。如果您已经在使用 Simple.Data,您可能会坚持使用更熟悉的 Simple.Data OData 适配器,尽管 Simple.OData.Client 支持更高级的 OData 协议特性,并且被打包为可移植类库,可以在 iOS 和 Android 等非 Windows 平台使用。它还提供了类型化或动态 API 的选择(因此它有效地提供了多种 API 风格)。我将展示使用类型化和动态 Simple.OData.Client 语法的示例,但每个示例都将以其 WCF Data Services 的对应项为起点。您可以自己判断哪个 API 效果更好。

比较中使用的库  

WCF Data Services 客户端 

Visual Studio 内置了为 OData 服务生成客户端访问代码的支持:只需从项目上下文菜单中选择“添加服务引用...”,然后输入现有 OData 服务的 URL。您将获得包含实体的代理代码(OData 服务资源),您将能够使用类似 Entity Framework 的 API 来操作它们。前提是您能够正确编写 LINQ 语句。为了获得最新的 WCF Data Services 客户端组件,请从 NuGet 安装它们

Simple.OData.Client  

Simple.OData.Client(也可从 NuGet 获取)是一个可移植类库,曾经从 Simple.Data OData 适配器中提取出来。它在 GitHub 上有自己的Wiki 页面,但我认为理解其特性的最简单方法是查看其测试。如前所述,它可以针对各种平台,包括 .NET 4.x、Windows Store、Silverlight 5、Windows Phone 8、iOS 和 Android。如果您需要消费由相互链接的资源集组成的 OData Feed,我认为它提供了最简单但功能强大的 API,具有类型化和动态风格。

检索数据

在 .NET 世界中,数据源通常会提供 LINQ 提供程序,有时甚至会为不以表格或关系形式公开数据的数据库编写 LINQ 提供程序。因此,Microsoft 开发人员为 OData 协议的客户端提供 LINQ 提供程序是合乎逻辑的,该提供程序已从“Astoria”项目发展成为 WCF Data Services。他们故意使客户端界面看起来像 LINQ to Entities,因此简单的 LINQ 语句与 LINQ to Entities 调用无法区分。

var product = ctx.Products.Where(x => x.ProductName == 
“Chai”).Single(); 

我在这里使用的是流畅的 LINQ 语法,但当然所有这些语句都可以使用查询推导式来编写。变量“ctx”代表负责将命令转换为 HTTP 请求并将其发送到 OData 服务的 OData 上下文。

正如我已经提到的,Simple.OData.Client 提供了几种语法风格,我们将重点关注其中两种:类型化和动态。到 OData 服务的类型化请求将如下所示:

var product = client.For<Products>().Filter(x => x.ProductName == 
“Chai”).FindEntry();

这是动态请求:

var x = ODataDynamic.Expression;
var product = 
client.For(x.Products).Filter(x.ProductName == “Chai”).FindEntry();

请注意,在使用动态 API 时,您需要先定义一个动态表达式对象(“x”)的实例,该对象可在代码块的其余部分(甚至在多次调用中)以各种表达式的形式使用,这些表达式在运行时进行评估并转换为 OData 命令的部分。

现在我们对如何使用不同的 OData 客户端库查询 OData 服务有了基本了解,让我引导您了解一些具体的示例,并展示为什么 WCF Data Services 客户端可能会失去其吸引力。

理由 1:投影到单个列 

WCF Data Services 

这是一个广泛使用的场景,因此它竟然不被 WCF Data Services 支持,这令人非常惊讶。这是一个例子: 

var productIDs = ctx.Products.Select(x => x.ProductID);

如果运行它,它将因以下错误而失败:

System.NotSupportedException : Individual properties can only be selected from a single resource or as part of a type. Specify a key predicate to restrict the entity set to a single instance or project the property into a named or anonymous type.

“作为类型的一部分”?让我们将其选择为匿名类型的一部分。然后它就可以工作了。

var productIDs = ctx.Products.Select(x => new { x.ProductID });

就像 Mauricio Scheffer 一样,我看不出这种限制有什么逻辑。LINQ 支持单列投影,WCF Data Services 服务器端也支持单列投影。这纯粹是客户端 LINQ 表达式处理器的限制,应该早就被修复了。

Simple.OData.Client(类型化) 

这是使用类型化实体的代码。

var productIDs = client.For<Products>().Select(x => x.ProductID).FindEntries(); 
Simple.OData.Client(动态) 

最后是动态版本的代码。

IEnumerable<dynamic> productIDs = client.For(x.Products).Select(x.ProductID).FindEntries();

请注意,我们可以使用“Products”而不是“Product”。像 Simple.Data 一样,Simple.OData.Client 有一个简单的内置单词匹配器,可以接受单数、复数和下划线分隔的名称,并尝试找到匹配的元数据元素。

理由 2:冗余查询条件

这可能不是一个常见场景,但当 LINQ 表达式在循环中构建时可能会发生,而且它是一个完全有效的 LINQ 表达式。它只是包含冗余的子句。

WCF Data Services

此代码将失败。

var product = ctx.Products.Where(x => x.ProductID == 1 && x.ProductID == 1).Single();
System.NotSupportedException : Multiple key predicates cannot be specified for the same entity set.

当然,您不能使用多个键来查找实体。但是,如果第二个键子句与第一个键子句相同,为什么不简单地优化表达式并消除冗余呢?Simple.OData.Client 可以很好地执行此操作。

Simple.OData.Client(类型化)
var product = client.For<Product>().Where(x => x.ProductID == 1 && x.ProductID == 1).FindEntry();
Simple.OData.Client(动态)
var product = client.For(x.Products).Select(x.ProductID == 1 && x.ProductID == 1).FindEntry();

理由 3:欺骗性的 LINQ 扩展方法

WCF Data Services

让我们看一下与上一个示例中使用的语句类似的语句。

var product = ctx.Products.Where(x => x.ProductID == 1).Single();

如果您的计算机上安装了 ReSharper,您应该会在该语句下看到绿色的波浪线。将鼠标悬停在上面,ReSharper 将显示一个工具提示,建议用一个调用替换 Where 和 Single 子句:“Replace with single call to Single(...)”。

这是大多数 LINQ 提供程序支持的自然代码简化。不幸的是,LINQ to OData 提供程序不支持此类语法优化,如果您按 Alt+Enter 接受建议并将代码替换为命令

var product = ctx.Products.Single(x => x.ProductID == 1);

……您将收到以下错误:

System.NotSupportedException : The method 'Single' is not supported.

太糟糕了。Single、First、Last、SingleOrDefault 等——所有这些 LINQ 扩展方法都必须在查询执行完成后应用于结果集合,您不能将其作为服务器请求的一部分发送。更糟糕的是,如果您将其中一个语句用作服务器请求,您不会收到任何警告——恰恰相反,正如我们刚才看到的,如果您不使用它们,您会收到 ReSharper 的通知。错误将在稍后,运行时发生。

Simple.OData.Client 使用不同的方法来检索单个和多个结果,以确保服务器端结果优化。

Simple.OData.Client(类型化)
// Returns first result 
var products = client.For<Product>().Where(x => x.ProductID == 1).FindEntry(); 
// Returns all results
var products = client.For<Product>().Where(x => x.ProductID == 1).FindEntries();

Simple.OData.Client(动态)

// Returns all results
var product = client.For(x.Products).Where(x.ProductID == 1).FindEntries();
// Returns all results
var product = client.For(x.Products).Where(x.ProductID == 1).FindEntry(); 

检索多个结果时,您当然可以将 Single/First/Last LINQ 扩展方法附加到语句上,就像使用 LINQ 提供程序一样。这些语句将在从服务器检索结果集后应用于结果集。

理由 4:交换命令子句

您应该没有任何理由不能交换 Where 和 Select 子句?让我们试试。

WCF Data Services
var productID = ctx.Products.Where(x => x.ProductID != 1).Select(x => new { ProductID }).First();

上述语句工作正常(请注意,我们在 Select 子句中使用了匿名类型,否则它将失败)。现在我们将 Where 应用于投影。

var productID = ctx.Products.Select(x => new { ProductID }).Where(x => x.ProductID != 1).First();

ReSharper 会立即建议将 Where 和 First 合并为一个子句,但我们知道这会失败。不幸的是,整个语句都会失败。

System.NotSupportedException : The filter query option cannot be specified after the select query option.

Simple.OData.Client 没有这个问题。

Simple.OData.Client(类型化) 
var productID = client.For<Product>().Where(x => x.ProductID != 1).Select(x => x.ProductID).FindEntry();
var productID = client.For<Product.Select(x => x.ProductID)>().Where(x => x.ProductID != 1).FindEntry();
Simple.OData.Client(动态)
var productID = client.For(x.Products).Where(x.ProductID != 1).Select(x.ProductID).FindEntry();
var productID = client.For(x.Products.Select(x.ProductID)).Where(x.ProductID != 1).FindEntry(); 

理由 5. 展开相关实体

虽然 OData 协议不支持 JOIN,但它有一个可以导航和展开的相关实体概念。因此,如果 Products 实体具有引用 Categories 实体的 CategoryID 列,并且服务元数据中定义了 Products.Category 关联,那么应该可以通过 Category 关联从 Products 导航到 Categories。

WCF Data Services

WCF Data Services LINQ 提供程序有一个自定义方法 Expand,可用于预加载相关实体。所以,如果我们想用关联的类别展开产品,我们可以使用以下语句:

var products = ctx.Products.Where(x => x.ProductName == “Chai”).Expand(x => x.Category);

不!它甚至无法编译。原因是在 Where 子句之后应用了自定义的 Expand 方法。“Where”是一个返回 IQueryable 的标准扩展方法,但 Expand 定义在 WCF Data Services 的一部分 DataServiceQuery 类上,因此它只能应用于生成的代理集合类(“Products”、“Categories”等),并且不能与标准的 LINQ 扩展方法互换。

再次,我们遇到了交换 LINQ 命令子句的问题,现在是由于使用了自定义方法。这是可行的语句:

var products = ctx.Products.Expand(x => x.Category).Where(x => x.ProductName == “Chai”);

但是如果我们想在此语句中添加投影子句并且只选择类别呢?让我们试试。

var categories = ctx.Products.Expand(x => x.Category).Where(x => x.ProductName == “Chai”).Select(x => x.Category);

嗯,出错了。

System.NotSupportedException : Can only specify query options (orderby, where, take, skip) after last navigation.

也许是因为我们在 Select 子句中没有使用匿名类型?好吧,再试一次。

var categories = ctx.Products.Expand(x => x.Category).Where(x => x.ProductName == “Chai”).Select(x => new { x.Category });

哎呀,这次错误消息不同了。

System.NotSupportedException : Cannot create projection while there is an explicit expansion specified on the same query.

但至少它给了我们一些线索。不允许组合 Expand 和 Select 子句。根据我的理解,但让我们去掉 Expand。

var categories = ctx.Products.Where(x => x.ProductName == “Chai”).Select(x => new { x.Category });

现在它工作了,但您刚刚看到了 WCF Data Services LINQ 提供程序的脆弱性。您只需要不断尝试不同的语法变体,直到找到在运行时不会失败的内容。

对于相关关系的预加载,Simple.OData.Client 定义了一个 Expand 方法,我们可以随意将其与其他流畅界面的方法交换,包括过滤和投影。

Simple.OData.Client(类型化)
var product = client.For<Products>().Expand(x => x.Category).Filter(x => x.ProductName == "Chai").FindEntry();

var product = client.For<Products>().Filter(x => x.ProductName == "Chai").Expand(x => x.Category).FindEntry();

var product = client.For<Products>().Expand(x => x.Category).Filter(x => x.ProductName == "Chai")
    .Select(x => new { x.ProductID, x.Category.CategoryID }).FindEntry();
Simple.OData.Client(动态) 
var product = client.For(x.Products).Expand(x.Category).Filter(x.ProductName == "Chai").FindEntry();

var product = client.For(x.Products).Filter(x.ProductName == "Chai").Expand(x.Category).FindEntry();

var product = client.For(x.Products).Expand(x.Category).Filter(x.ProductName == "Chai")
    .Select(x.ProductID, x.Category.CategoryID).FindEntry();

理由 6:导航到相关实体

除了展开关系,OData 协议还支持导航到相关实体,因此应该可以绕过父条目并直接访问其关系。搜索条件有一个限制:父条目应按其键查找,因此导航是从单个条目执行的。

WCF Data Services

WCF Data Services 在严格意义上不支持导航,但如上一节所述,可以使用属性为关系的投影来模拟导航。

var categories = ctx.Products.Where(x => x.ProductID == 1).Select(x => new { x.Category });

这不是真正的导航到相关实体,原因有两个。首先,该语句返回匿名类型集合,而不是类别对象,因此我们需要通过匿名类型的 Category 属性访问实际的 Category 对象。其次,该语句生成不同的 OData 命令:filter 而不是 key lookup。所以原则上,您可以检索多个产品的类别,例如通过发出以下命令:

var categories = ctx.Products.Where(x => x.ProductName != 1).Select(x => new { x.Category });

与 WCF Data Services 客户端不同,Simple.OData.Client 具有通过 NavigateTo 方法的正确导航支持。

Simple.OData.Client(类型化)
var category = client.For<Product>().Where(x => x.ProductName == “Chai”)
    .NavigateTo<Category>().FindEntry();
Simple.OData.Client(动态) 
var category = client.For(x.Product).Where(x.ProductName == “Chai”)
    .NavigateTo(x.Category).FindEntry();

理由 7:链式导航

WCF Data Services

由于缺乏正确的导航支持,WCF Data Services 客户端无法用于获取多级导航的结果。您只能检索属于直接后代的信息,尝试访问更深的级别将不起作用。

var product = ctx.Products.Where(x => x.ProductID == 1)
    .Select(x => new { x.Category, x.Category.Products })

上述语句将成功,但 product.Category.Products 将不包含任何元素。出于相同的原因,以下语句都将以“序列不包含元素”异常失败:

var employee = ctx.Employees.Where(x => x.EmployeeID == 14) 
    .Select(x => new { x.Superior })
    .Select(x => new { x.Superior.Superior }).First();

var employees = ctx.Employees.Where(x => x.EmployeeID == 14)
    .Select(x => new { x.Superior })
    .Select(x => new { x.Superior.Subordinates });

Simple.OData.Client 是另一回事:它完全支持导航,因此上述所有场景都可以使用类型化和动态语法轻松表达。

Simple.OData.Client(类型化)
var products = client.For<Products>().Filter(x => x.ProductID == 1)
    .NavigateTo(x => x.Category)
    .NavigateTo(x => x.Products)
    .FindEntries();

我们可以进行更深入的导航,从员工导航到其上级的上级。

var employee = client.For<Employees>().Filter(x => x.EmployeeID == 14)
    .NavigateTo(x => x.Superior)
    .NavigateTo(x => x.Superior)
    .FindEntry();

或者,如果我们请求上级的下属,我们可以获取多个结果。

var employees = client.For<Employees>().Filter(x => x.EmployeeID == 14)
    .NavigateTo(x => x.Superior)
    .NavigateTo(x => x.Subordinates)
    .FindEntries();
Simple.OData.Client(动态) 这里是动态 API 的类似语句。
var products = client.For(x.Products).Filter(x.ProductID == 1)
    .NavigateTo(x.Category)
    .NavigateTo(x.Products)
    .FindEntries();
var employee = client.For(x.Employees).Filter(x.EmployeeID == 14)
    .NavigateTo(x.Superior) 
    .NavigateTo(x.Superior)
    .FindEntry();
var employees = client.For(x.Employees).Filter(x.EmployeeID == 14)
    .NavigateTo(x.Superior)
    .NavigateTo(x.Subordinates)
    .FindEntries();

理由 8:选择派生类型的实体 

让我们简要看一下一个相当高级(且在 OData 服务中很少使用)的场景:OData 资源类型的子类化。我个人不喜欢将 OOP 引入 RESTful(或类似 REST)的 Web 服务,但由于 OData 协议支持基类和派生类,因此公平地检查我们的库如何处理它们。

到目前为止,我一直使用经典的 Northwind 模型,但它没有派生类。所以我用 Transport 和 Ship 实体类型扩展了它:Ship 是 Transport 的子类,并添加了 ShipName 属性。基类上的 TransportType 属性用作类型鉴别器。

WCF Data Services

选择一个 Transport 实例而不考虑其类型是很直接的。

var transport = ctx.Transport.First();

接下来是仅选择 Ship。

var ship = ctx.Transport.Where(x => x is Ships).First();

要访问 ShipName 属性,我们需要转换结果。

var shipName = ctx.Transport.Where(x => x is Ships).First() as Ships;

如果我们想按 Ship 名称搜索呢?这也可以。

var ship = ctx.Transport.Where(x => x is Ships && (x as Ships).ShipName == “Titanic”).First();

从最后一个例子,您可能会认为您可以使用 Select 子句将结果转换为派生类型,从而获得正确类型的结果。

var ships = ctx.Transport.Where(x => x is Ships).Select(x => x as Ships);

但这将不起作用。

System.NotSupportedException : Unsupported expression '(x As Ships)' in 'Select' method. Expression cannot end with TypeAs.

您始终以基类型获得结果,并且需要在客户端将其转换为派生类型。然而,与我们在其他查询中遇到的问题相比,WCF Data Services 中的派生类型管理几乎如预期般工作。但是,让我们看看 Simple.OData.Client 中是如何实现的,我相信这样可以更清楚地看出哪种语法更直观。

Simple.OData.Client(类型化)

Simple.OData.Client 有一个泛型方法 As<T>,它重新定义了正在处理的实体的类型。一旦执行了转换,流式链上的所有后续调用都将使用该类型进行。

var transports = client.For<Transport>().FindEntries();
var ships = client.For<Transport>().As<Ships>().FindEntries();
var ship = client.For<Transport>().As<Ships>().Filter(x => x.ShipName == "Titanic").FindEntry();
Simple.OData.Client(动态)

以类似的方式,动态 Simple.OData.Client API 有一个 As 方法,只是这个方法不是泛型的,并且派生实体类型是根据动态表达式参数进行评估的。

var transports = client.For(x.Transport).FindEntries();
var ships = client.For(x.Transport).As(x.Ships).FindEntries();
var ship = client.For(x.Transport).As(x.Ships).Filter(x.ShipName == "Titanic").FindEntry();

修改数据

WCF Data Services 客户端使用与 Entity Framework 相同的​​方法来添加、更新和删除 OData 资源,它还有一个数据上下文的概念,因此所有操作都会排队等待调用 SaveChanges,然后才发送到 OData 服务。Simple.OData.Client 没有数据上下文的概念(尽管可以根据 OData 协议将多个操作打包到批处理中),但最终,这两个库提供的数据修改方法都映射到等效的 HTTP 请求。

这是使用 WCF Data Services 客户端创建条目的示例。

var product = new Products { ProductName = "Milk" };
ctx.AddToProducts(product); 
ctx.SaveChanges();

这是 Simple.OData.Client 的对应项。

var product = client.For<Products>().Set(new { ProductName = “Milk” }).InsertEntry();

var product = client.For<Products>().Set(new Products { ProductName = “Milk” }).InsertEntry();

var product = client.For(x.Products).Set(x.ProductName = “Milk”).InsertEntry();

一个重要的区别是,在 WCF Data Services API 中,上下文扮演着核心角色。它是一个容器,一个操作和实体的堆栈。您打算做的任何事情都必须通过上下文,它需要跟踪所有实体才能将它们发送到服务器,并且由开发人员负责确保实体被跟踪。这使得它与能够自动跟踪受影响实体的 Entity Framework 有很大不同。只要您处理的是独立的实体,它就不会造成不便。但关系管理有一些陷阱。让我们看看其中的一些,并与使用 Simple.OData.Client 如何做到这一点进行比较。

理由 9:分配关联实体

WCF Data Services

以下代码看起来合理,只是它不起作用。

var category = ctx.Categories.Where(x => x.CategoryName == "Beverages").Single();
var product = new Products { ProductName = “Chai”, Category = category };
ctx.AddToProducts(product);
ctx.SaveChanges(); 

代码不会失败,产品也会被创建,但它没有链接到类别。与检测关联属性赋值的 Entity Framework 不同,上下文(“ctx”)需要明确通知有关链接产品的意图。因此,我们需要修改代码,如果您查找可用选项,会发现几个选择:AddLink、AddRelatedObject 和 SetLink。每种场景的正确选择取决于关系的基数。在上例中,SetLink 是正确的,因此有效代码如下所示:

var category = ctx.Categories.Where(x => x.CategoryName == "Beverages").Single();
var product = new Products { ProductName = “Chai”, Category = category };
ctx.AddToProducts(product);
ctx.SetLink(product, "Category", category);
ctx.SaveChanges();

有几个原因我不喜欢这种方法:

  • 库可以更智能地找出对象具有关联属性已赋值,并将额外的链接代码的负担从开发人员身上转移走;
  • 服务元数据包含有关关系基数的所有信息,因此无需强制开发人员明确他们正在建立什么类型的关系;
  • 使用魔法字符串很糟糕。WCF Data Services 在其他方法中使用 lambda 表达式,它也应该允许在这里使用 lambda。

现在让我们看看 Simple.OData.Client 如何管理链接。

Simple.OData.Client(类型化)
var category = client.For<Categories>().Filter(x => x.CategoryName == "Beverages").FindEntry();
var product = client.For<Products>().Set(new { ProductName = "Chai", Category = category }).InsertEntry();

能有比这更简单的吗?这是动态版本。

Simple.OData.Client(动态)
var category = client.For(x.Categories).Filter(x.CategoryName == "Beverages").FindEntry();
var product = client.(x.Products).Set(new { ProductName = "Chai", Category = category }).InsertEntry(); 

理由 10:分离关联实体

WCF Data Services

我猜您已经准备好,分离链接不会很容易,我可以向您保证,它不会。事实上,它比我想象的还要困难。不用说,以下代码将不起作用:

var product = ctx.Products.Where(x => x.ProductName == "Chai").Single();
product.Category = null;
ctx.UpdateObject(product);
ctx.SaveChanges();

代码不会失败,它只是不起作用。同样,由于上下文不知道(或者我应该说无知)。但是,在上一节了解了 SetLink 方法后,我们可能会尝试这样做:

var product = ctx.Products.Where(x => x.ProductName == "Chai").Single();
ctx.SetLink(product, "Category", null);
ctx.UpdateObject(product);
ctx.SaveChanges();

不。也无效。WCF Data Services 上下文不将 null 关联对象视为分离链接的意图。但有一个 DetachLink 方法,让我们试试吧:

ctx.DetachLink(product, "Category", category);

不,关系仍然存在。也许是 DeleteLink?

ctx.DeleteLink(product, "Category", category);

这次代码在运行时失败。

System.InvalidOperationException : AddLink and DeleteLink methods only work when the sourceProperty is a collection.

让我们修改它,以便从类别中删除链接。

ctx.DeleteLink(product, "Category", category);

不,如果我重新加载产品,它仍然显示链接的类别。但这显然是由于上下文对象中的内部缓存。如果我清除上下文并重新加载产品,链接就会消失。因此,经过一些挫折后,这就是有效代码:

var category = ctx.Categories.Where(x => x.CategoryName == "Beverages").Single();
var product = ctx.Products.Where(x => x.ProductName == "Chai").Single();
ctx.DeleteLink(category, "Products", product);
ctx.UpdateObject(product);
ctx.SaveChanges();
ctx.DetachProduct(); // Without this call if you reload the project it will still show a linked category!

好吧,您可以自己判断上述代码有多直观。现在看看使用 Simple.OData.Client 如何实现同样的事情。

Simple.OData.Client(类型化)
var product = client.For<Products>().Filter(x => x.ProductName == "Chai").FindEntry();
var category = client.For<Categories>().Filter(x => x.CategoryName == "Beverages").FindEntry();
client.For<Products>().Key(product.ProductID).UnlinkEntry(x => x.Category);

注意 Key 子句的使用。我也可以使用 Filter 子句,只要 filter 条件代表键查找。

Simple.OData.Client(动态)
var product = client.For(x.Products).Filter(x.ProductName == "Chai").FindEntry();
var category = client.For(x.Categories).Filter(x.CategoryName == "Beverages").FindEntry();
client.For(x.Products).Key(product.ProductID).UnlinkEntry(x.Category);

使用 UnlinkEntry 方法的替代方法是将 Category 属性设置为 null 并调用 UpdateEntry。

理由 11:修改多个条目

Entity Framework 和 WCF Data Services 客户端都没有内置支持根据搜索条件更新多个条目。OData 协议也不提供此功能——您必须获取查询结果(或至少其键值),然后逐个更新。但这并非不常见的场景,当您需要更改或删除所有符合特定标准的实体时,Simple.OData.Client 提供了此功能。

WCF Data Services

正如我所说,WCF Data Services 客户端没有提供一个方法来一次性修改或删除查询执行的多个结果,所以您需要这样做:

foreach (var product in ctx.Products.Where(x => x.ProductName.StartsWith(“A”))
{ 
    ctx.DeleteObject(product);
}
ctx.SaveChanges();

Simple.OData.Client 提供了 UpdateEntries 和 DeleteEntries 方法,负责查询 OData 服务和遍历结果集。以下是示例。

Simple.OData.Client(类型化)
client.For<Products>().Filter(x => x.ProductName.StartsWith(“A”)).DeleteEntries();
Simple.OData.Client(动态)
client.For(x.Products).Filter(x.ProductName.StartsWith(“A”)).DeleteEntries();

最终想法 

理由 12:REST 不是 SOAP 

我们已经回顾了几个示例,展示了使用 WCF Data Services 客户端读取和更新 OData Feed 的障碍。但是还有一个,从概念上讲。Microsoft 选择将 OData 服务呈现给 Visual Studio 用户,就像它们是传统的 SOAP 服务一样,使用其 WSDL 文件进行描述。我相信大多数首次接触 OData 服务并从项目菜单中选择“添加服务引用”的开发人员甚至没有意识到他们正在接触 REST 通信。在 Visual Studio 内部,一切看起来都像传统的 WCF 服务,具有契约和数据类型。当然,OData 资源类型可以被看作是带有 DataContract 属性的类,HTTP 动词 GET、POST、PUT/MERGE 和 DELETE 类似于 WCF 服务操作。但是与 WCF、SOAP 甚至 Entity Framework 的类比是误导性的。我宁愿看到更好地公开 HTTP 动词,这样开发人员就能清楚地了解哪些操作是安全的、幂等的或两者都不是。

虽然 Simple.OData.Client API 不包含以 HTTP 动词命名的​​方法,但它有与它们直接映射的方法。FindEntries 和 FindEntry 对应于 GET,它们分别检索所有匹配结果或第一个匹配结果。InsertEntry、UpdateEntry 和 DeleteEntry 分别映射到 POST、PUT/MERGE 和 DELETE。LinkEntry 和 UnlinkEntry 提供了对 HTTP 动词调用的更高抽象,以建立或删除实体之间的关系。这些方法(都命名为 <Verb>Entry 或 <Verb>Entries)总是 Simple.OData.Client 流式接口调用链中的最后子句。调用链中的除最后一个元素之外的所有元素都会逐步构建 OData 请求,而对 <Verb>Entry 的最终调用将调用相应的 HTTP 命令。我认为这样的 API 能够更好地理解底层的 HTTP 通信,并帮助开发人员有效地利用 OData 协议。

© . All rights reserved.