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

高级 .NET OData 使用:WCF Data Services

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (26投票s)

2010 年 12 月 10 日

Ms-PL

9分钟阅读

viewsIcon

234589

downloadIcon

6171

展示 .NET (WCF Data Services) 中 OData 用法的示例;示例按复杂度的递增顺序排列,涵盖越来越高级的场景。

引言

在本文中,我将提供代码示例,展示 OData 在 .NET (WCF Data Services) 中的用法。这些示例将按复杂度的递增顺序进行,涵盖越来越高级的场景。

背景

有关 OData 的背景信息,我在几个月前写了一篇关于 OData 的入门文章:Open Data Protocol (OData) 概述。在那篇文章中,我展示了 Open Data Protocol 是什么以及如何用浏览器使用它。

在本文中,我将展示如何使用 .NET Framework 4.0 将数据公开为 OData。在 .NET Framework 4.0 中用于公开 OData 的技术称为 WCF Data Services。在 .NET 4.0 之前,它称为 ADO.NET Data Services,在此之前,它称为 Astoria,这是 Microsoft 的内部项目名称。

使用代码

代码示例包含一个 Visual Studio 2010 解决方案。该解决方案包含我在此处提供的每个示例的一个项目。

该解决方案还包含一个名为“DB Scripts”的解决方案文件夹,其中包含三个文件:SchemaCreation.sql(在预先存在的数据库中创建包含三个表的架构)、DataInsert.sql(在表中插入示例数据)和SchemaDrop.sql(在需要时删除表和架构)。

我们对整个项目使用相同的数据库架构

Schema.JPG

Hello World:您的数据库上网

对于第一个示例,我们将做一个 Hello World:我们将把我们的架构公开到网上。为此,我们需要

  • 创建架构的 Entity Framework 模型
  • 创建 WCF Data Service
  • 将数据上下文映射到我们的实体模型
  • 声明权限

创建 Entity Framework 模型不是本文的目标。您可以查阅 MSDN 页面以获取详细信息:http://msdn.microsoft.com/en-us/data/ef.aspx

EntityModel.JPG

创建 WCF Data Service 也非常简单;在项目中创建一个新项

现在,我们需要将数据上下文映射到我们的实体模型。这可以通过编辑服务的代码隐藏来实现,只需替换

public class EmployeeDataService : DataService<
/* TODO: put your data source class name here */ >

by

public class EmployeeDataService : DataService<ODataDemoEntities>

最后,我们现在需要声明权限。默认情况下,Data Services 是锁定的。我们可以打开读取和写入权限。在本文中,我们将重点关注读取权限。我们只需替换

// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
  // TODO: set rules to indicate which entity sets
  // and service operations are visible, updatable, etc.
  // Examples:
  // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
  // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
  config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}

by

// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
  config.SetEntitySetAccessRule("Employees", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Addresses", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Departments", EntitySetRights.AllRead);

  config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}

好了;您可以运行您的项目,并将三个表作为 OData Feed 在网上公开。

公开另一个数据模型

不幸的是,前面的例子是网上 95% 的示例都围绕的内容。但 WCF Data Services 可以远不止于此。

在本节中,我们将从头开始构建一个数据模型,一个甚至不连接到数据库的模型。我们可以连接到什么……当然!您电脑上运行的进程列表!

为此,我们将需要

  • 创建一个进程模型类
  • 创建一个公开进程的数据模型
  • 创建 WCF Data Service
  • 将数据上下文映射到我们的数据模型
  • 声明权限

我们按以下方式创建进程模型类

 /// <summary>Represents the information of a process.</summary>
[DataServiceKey("Name")]
public class ProcessModel
{
  /// <summary>Name of the process.</summary>
  public string Name { get; set; }

  /// <summary>Process ID.</summary>
  public int ID { get; set; }
}

那么这里需要注意的关键点是

  • 始终使用简单类型(例如,整数、字符串、日期等):它们是唯一可以映射到 Entity Data Model (EDM) 类型的类型。
  • 始终使用 `DataServiceKey` 属性声明键属性(相当于数据库表中的主键列)。键的概念是 OData 的核心,因为您可以通过 ID 来单独标识一个实体。

如果您违反了这两个规则中的任何一个,您的 Web Service 都会出现错误,而且很难知道原因。

然后,我们按以下方式创建数据模型,公开进程

/// <summary>Represents a data model exposing processes.</summary>
public class DataModel
{
  /// <summary>Default constructor.</summary>
  /// <remarks>Populates the model.</remarks>
  public DataModel()
  {
    var processProjection = from p in Process.GetProcesses()
                            select new ProcessModel
                            {
                              Name = p.ProcessName,
                              ID = p.Id
                            };

    Processes = processProjection.AsQueryable();
  }

  /// <summary>Returns the list of running processes.</summary>
  public IQueryable<ProcessModel> Processes { get; private set; }
}

这里,为了完整起见,我们可以公开不止一个进程,但我们选择了简单。

这里的关键是

  • **只**拥有返回模型 `IQueryable` 的属性。
  • 在构造函数中填充这些集合

这里我们直接填充模型列表,但有时(正如我们将在下一节中看到的),您可以简单地用延迟查询填充它,这更有效率。

数据服务和权限声明的创建和映射与前面的示例相同。完成这些操作后,您将拥有一个 OData 端点,该端点公开您计算机上的进程。您可以使用任何类型的客户端(如 LinqPad)来查询此端点。

这个例子本身没什么用,但它表明您可以将任何类型的数据公开为 OData 端点。这非常强大,因为 OData 是一种新兴标准,而且正如您所见,以这种方式公开数据非常容易。

例如,您可以让您的生产服务器公开一些实时数据(例如,活动会话数量)作为 OData,您可以在任何时候使用它。

公开数据库的转换

另一个非常有用的场景,某种程度上是前面场景的组合,是通过转换公开数据库中的数据。现在,这可以通过在实体模型中执行映射来实现,但有时您可能不想直接公开实体模型,或者您可能无法在实体模型中进行映射。例如,OData 数据对象可能不在您的控制范围内,但您必须使用它们来公开数据。

在这个示例中,我们将在 OData 级别将员工及其地址展平为一个实体。

  • 创建架构的 Entity Framework 模型
  • 创建一个员工模型类
  • 创建一个部门模型类
  • 创建一个公开这两个模型类的数据模型
  • 创建 WCF Data Service
  • 将数据上下文映射到我们的数据模型
  • 声明权限

实体模型(Entity Framework model)的创建与“Hello World”部分相同。

我们按以下方式创建员工模型类

 /// <summary>Represents an employee.</summary>
[DataServiceKey("ID")]
public class EmployeeModel
{
  /// <summary>ID of the employee.</summary>
  public int ID { get; set; }

  /// <summary>ID of the associated department.</summary>
  public int DepartmentID { get; set; }

  /// <summary>ID of the address.</summary>
  public int AddressID { get; set; }

  /// <summary>First name of the employee.</summary>
  public string FirstName { get; set; }

  /// <summary>Last name of the employee.</summary>
  public string LastName { get; set; }

  /// <summary>Address street number.</summary>
  public int StreetNumber { get; set; }

  /// <summary>Address street name.</summary>
  public string StreetName { get; set; }
}

我们包含了员工和地址的属性,从而展平了这两个模型。我们还将 EmployeeID 重命名为 ID。

我们按以下方式创建部门模型类

 /// <summary>Represents a department.</summary>
[DataServiceKey("ID")]
public class DepartmentModel
{
  /// <summary>ID of the department.</summary>
  public int ID { get; set; }

  /// <summary>Name of the department.</summary>
  public string Name { get; set; }
}

我们按以下方式创建一个公开两个模型的数据模型

/// <summary>Represents a data model exposing
/// both the employee and the department.</summary>
public class DataModel
{
  /// <summary>Default constructor.</summary>
  /// <remarks>Populates the model.</remarks>
  public DataModel()
  {
    using (var dbContext = new ODataDemoEntities())
    {
      Departments = from d in dbContext.Department
                    select new DepartmentModel
                    {
                      ID = d.DepartmentID,
                      Name = d.DepartmentName
                    };

      Employees = from e in dbContext.Employee
                  select new EmployeeModel
                  {
                    ID = e.EmployeeID,
                    DepartmentID = e.DepartmentID,
                    AddressID = e.AddressID,
                    FirstName = e.FirstName,
                    LastName = e.LastName,
                    StreetNumber = e.Address.StreetNumber,
                    StreetName = e.Address.StreetName
                  };
    }
  }

  /// <summary>Returns the list of employees.</summary>
  public IQueryable<EmployeeModel> Employees { get; private set; }

  /// <summary>Returns the list of departments.</summary>
  public IQueryable<DepartmentModel> Departments { get; private set; }
}

我们基本上是在填充员工查询时进行映射。这里,与前面的示例相反,我们没有实际填充员工,而是定义了一个查询来获取它们。由于 LINQ 始终定义一个延迟查询,因此查询只是映射信息。

然后,我们按以下方式创建 WCF Data Service,将数据上下文映射到数据模型,并声明权限

public class EmployeeDataService : DataService<DataModel>
{
  // This method is called only once to initialize service-wide policies.
  public static void InitializeService(DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("Employees", EntitySetRights.AllRead);
    config.SetEntitySetAccessRule("Departments", EntitySetRights.AllRead);

    config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
  }
}

这个场景非常强大。基本上,您可以混合来自数据库和其他源的数据,对其进行转换,并将其公开为 OData,同时仍然受益于模型(例如数据库)的查询能力。

服务操作

WCF Data Services(.NET 4.0 的 OData 实现)涵盖的另一个有用场景是能够公开一个带有输入参数的服务,但其输出被视为 OData 中的其他实体集,也就是说,可以进行各种查询。

这非常强大,因为 OData 中的查询表达能力远不如 LINQ,也就是说,有很多 LINQ 查询是您无法通过 OData 完成的。这很容易理解,因为查询被打包在一个 URL 中。服务操作弥补了这一差距,允许您接收参数,执行复杂的 LINQ 查询,并将结果返回为可查询的实体集。

为什么您要查询一个操作作为查询的结果?首先,您可能想使用 _take_ 和 _skip_ 对结果进行分页。但也有可能结果仍然代表大量数据,而您只对其中一小部分感兴趣。例如,您可以有一个服务操作,返回公司中病假少于给定数量的个人;对于一家大公司来说,这仍然是大量数据!

在这个示例中,我们将公开一个服务操作,该操作以员工数量为输入,并返回至少有这么多员工的部门。

  • 创建数据库架构的 Entity Framework 模型
  • 创建 WCF Data Service
  • 将数据上下文映射到我们的实体模型
  • 添加一个服务操作
  • 声明权限

前三个步骤与“Hello World”示例相同。

我们按以下方式在数据服务中定义一个服务操作

[WebGet]
public IQueryable<Department> GetDepartmentByMembership(int employeeCount)
{
  var departments = from d in this.CurrentDataSource.Department
                    where d.Employee.Count >= employeeCount
                    select d;

  return departments;
}

然后我们按以下方式添加安全措施

// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
  config.SetEntitySetAccessRule("Employee", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Address", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Department", EntitySetRights.AllRead);

  config.SetServiceOperationAccessRule("GetDepartmentByMembership", 
                                       ServiceOperationRights.AllRead);

  config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}

请注意,我们也需要启用对服务操作的读取权限。

然后,我们可以使用类似以下的 URL 访问服务操作:_https://:37754/EmployeeDataService.svc/GetDepartmentByMembership?employeeCount=2_。

您可以在这篇 MSDN 文章中阅读更多关于服务操作的信息。

使用 .NET Framework 消耗 OData

要消耗 OData,您可以使用任何可用的 Web 库通过 HTTP-GET 直接访问 URL。在 .NET 中,您可以做得更好。

您只需为 OData 服务添加一个引用,Visual Studio 就会为您生成代理。这很快很方便,而且效果非常好。

在您定义了自己的数据模型(如我们的数据库转换示例)的情况下,您可能希望将这些模型作为数据契约在服务器和客户端之间共享。然后,您需要自己定义代理,这并不难

/// <summary>Proxy to our data model exposed as OData.</summary>
public class DataModelProxy : DataServiceContext
{
  /// <summary>Constructor taking the service root in parameter.</summary>
  /// <param name="serviceRoot"></param>
  public DataModelProxy(Uri serviceRoot)
  : base(serviceRoot)
  {
  }

  /// <summary>Returns the list of employees.</summary>
  public IQueryable<EmployeeModel> Employees
  {
    get { return CreateQuery<EmployeeModel>("Employees"); }
  }

  /// <summary>Returns the list of departments.</summary>
  public IQueryable<DepartmentModel> Departments
  {
    get { return CreateQuery<DepartmentModel>("Departments"); }
  }
}

基本上,我们派生自 `System.Data.Services.Client.DataServiceContext`,并为每个实体集定义一个属性,并为每个实体集创建一个查询。然后我们可以这样使用它

static void Main(string[] args)
{
  var proxy = new DataModelProxy(new Uri(
      @"https://:9793/EmployeeDataService.svc/"));
  var employees = from e in proxy.Employees
                  orderby e.StreetName
                  select e;

  foreach (var e in employees)
  {
    Console.WriteLine("{0}, {1}", e.LastName, e.FirstName);
  }
}

代理基本上充当数据上下文!我们将它视为任何实体集源,并可以在上面进行查询。这非常强大,因为我们不必自己将查询转换为 URL:平台会处理这一切!

结论

我们已经看到了使用 WCF Data Services 公开和消耗数据的各种场景。我们看到,没有必要将自己局限于数据库或实体框架模型。我们还可以公开服务操作来执行通过 OData 否则无法完成的查询,并且我们看到了在 .NET 客户端上消耗 OData 的简便方法。

我希望这能为您打开 OData 的可能性。我们通常会看到将数据库公开到网上的示例,看起来就像回到了 1995 年的 Access。但 OData 远不止于此:它使您能够将数据公开到网上,但可以按照您想要的方式呈现,并控制其访问。使用 OData 公开数据非常快,而且您不需要了解客户端的查询需求,因为协议会处理这一切。

© . All rights reserved.