首页数据库类 mssql → 讲述如何使用SQL CLR表值函数进行扩展

讲述如何使用SQL CLR表值函数进行扩展

日期:2007-4-29 08:57:36 出处:microsoft.com 作者:microsoft.com 人气:
上一页 [1] [2] 下一页

Web 服务和表值函数

表值函数的一个有趣功能是从 Web 服务中提取数据的能力。这可用于创建非常规的报表。我将说明如何在表值函数中使用 Microsoft MapPoint Web 服务,并将它与 AdventureWorks 数据库中的数据联接,从而将空间数据显示在地图上和显示在报表中。

使用 MapPoint Web 服务:

您必须先从 Microsoft 获得免费的开发人员帐户,然后才能开始按照 MapPoint Web 服务进行开发。您可以访问 MapPoint Web 服务客户服务网站,从中获取一个帐户。开始根据该 Web 服务进行开发之前,可以从一个好地方获取信息,即 MapPoint Web 服务 SDK 入门。

您需要使用 Visual Studio 将 Web 引用添加到您的项目中,该引用指向 MapPoint 暂存服务器上的 .wsdl 文件。有关为 MapPoint Web 服务配置 Web 引用的详细信息,请参阅访问 MapPoint Web 服务 SOAP API。

MapPoint Web 服务提供四种服务,每种服务都有其自己的 SOAP 端点:

• 公共服务提供可供其他服务使用的功能。此服务用于检索元数据和用于实用功能。

• 查找服务可用于搜索位置,查找某一地址的经度和纬度(“地理编码”),以及查找某位置附近的兴趣点。

• 路线服务指示一个位置到另一个位置的行车路线。

• 呈现服务可用于利用位置和路线信息创建地图图像。

MapPoint Web 服务表值函数:

最后,我希望我的表值函数使用 MapPoint Web 服务来执行以下任务:

• 使用查找服务来查找 AdventureWorks 自行车店的经度和纬度。

• 使用查找服务来查找距此经度和纬度最近的五台自动柜员机 (ATM)。

• 使用呈现服务来查找从该商店到 ATM 的路线。

• 使用呈现服务将此路线显示在地图上。

首先,我必须定义一个称为 GetProximity 的表值函数。以下 Transact-SQL 代码显示我的表值函数的签名:

CREATE FUNCTION GetProximity(@city nvarchar(200), @state nvarchar(2),
   @count int, @entityTypeName nvarchar(200))
RETURNS TABLE 
(HitName nvarchar(200), HitAddress nvarchar(200), MapImage 
varbinary(max))

GetProximity 使用城市名和两位数的州代码来代表初始位置。它返回实体数,和要搜索的实体类型名。它会搜索 n 个最近的实体,其中,n 由计算参数指定,实体类型由 entityTypeName 参数指定。它会返回一个表和一幅地图(二进制文件图像),表中包含名称列、地址列,地图中包含每个实体的路线。

C# 方法签名如下所示:

public static IEnumerable 
InitMap(string city, string state, int count,
   string entityTypeName)
public static void FillRow(Object obj, 
out SqlChars name, out SqlChars
   address, out SqlBinary map)

请注意,nvarchar Transact-SQL 数据类型映射到 SqlChars .NET Framework 数据类型,而 varbinary Transact-SQL 数据类型映射到 SqlBinary .NET Framework 数据类型。有关数据类型之间的映射的完整列表,请参阅 System.Data.SqlTypes 命名空间的文档。

在 InitMap 方法中,我将城市和州转换成经度和纬度。接着,我找到了与此坐标接近的所有实体。最后,我找到了初始位置和找到的实体之间的行车路线。返回值是封装有行车路线的一组 Route 对象。

public static IEnumerable 
InitMap(string city, string state, 
int count, string entityTypeName)
{
   FindServiceSoap find = new FindServiceSoap();
   find.PreAuthenticate = true;
   find.Credentials = new NetworkCredential(username, passwd);
      
   // 对初始城市和州进行地理编码(Geocode)
   FindAddressSpecification findSpec 
= new FindAddressSpecification();
   Address findAddr = new Address();
   findAddr.CountryRegion = "US";
   findAddr.Subdivision = state;
   findAddr.PrimaryCity = city;
   findSpec.InputAddress = findAddr;
   findSpec.DataSourceName = "MapPoint.NA";
   findSpec.Options = new FindOptions();
   findSpec.Options.ThresholdScore = 0.45;
   FindResults results = find.FindAddress(findSpec);

   if (results.NumberFound > 0)
   {
      // 如果城市和州已经存在,则获取经度和纬度
      Location startLocation = results.Results[0].FoundLocation;
      LatLong startPoint = startLocation.LatLong;

      // 查找附近的实体
      FindNearbySpecification findNearby = new 
FindNearbySpecification();
      FindFilter filter = new FindFilter();
      filter.EntityTypeName = entityTypeName;
      findNearby.Filter = filter;

      FindOptions options = new FindOptions();
      options.Range = new FindRange();
      // 设置计数限制
      options.Range.Count = count;
      findNearby.Options = options;
      findNearby.DataSourceName = "NavTech.NA";
      findNearby.LatLong = startPoint;
      findNearby.Distance = 10.0;
      results = find.FindNearby(findNearby);

      Route[] routes = new Route[results.Results.Length];
      RouteServiceSoap routeService = new RouteServiceSoap();

      routeService.PreAuthenticate = true;
      routeService.Credentials = new NetworkCredential(username,passwd);

      RouteSpecification spec = new RouteSpecification();
      spec.DataSourceName = "MapPoint.NA";

      // 创建到每个实体的路线
      spec.Segments = new SegmentSpecification[2];
      spec.Segments[0] = new SegmentSpecification();
      spec.Segments[0].Waypoint = new Waypoint();
      spec.Segments[0].Waypoint.Location = startLocation;
      spec.Segments[0].Waypoint.Name = "start";
      for (int x = 0; x < results.Results.Length; x++)
      {
         spec.Segments[1] = new SegmentSpecification();
         spec.Segments[1].Waypoint = new Waypoint();
         spec.Segments[1].Waypoint.Location = 
results.Results[x].FoundLocation;
         spec.Segments[1].Waypoint.Name = "end";
         routes[x] = routeService.CalculateRoute(spec);
      }
      return routes;
   }
   return null;
}

在 FillRow 方法中,我使用呈现服务将每个 Route 对象转换成了地图图像。然后使用这一图像和该实体的位置数据填充行。

public static void FillRow(Object obj, out SqlChars name, out SqlChars 
address, out SqlBinary map)
{
   Route route = (Route)obj;

   // 构建地址字符串
   Address endAddress = 
route.Specification.Segments[1].
Waypoint.Location.Address;
   string entityAddress = endAddress.AddressLine;
   string enitityCity = endAddress.PrimaryCity;
   string entityState = endAddress.Subdivision;
   string entityName = 
route.Specification.Segments[1].
Waypoint.Location.Entity.DisplayName;

   // 对两列的值进行分配
   name = new SqlChars(entityName);
   address = new SqlChars(entityAddress + ' ' + enitityCity + ' ' + 
entityState);

   // 获取路线视图
   ViewByHeightWidth view = route.Itinerary.View.ByHeightWidth;
   RenderServiceSoap renderService = new RenderServiceSoap();

   renderService.PreAuthenticate = true;
   renderService.Credentials = new NetworkCredential(username, passwd);

   // 显示带有路线的地图
   MapSpecification mapSpec = new MapSpecification();
   mapSpec.DataSourceName = "MapPoint.NA";
   mapSpec.Views = new MapView[]{view};
   mapSpec.Route = route;

   // 将地图指定给地图列
   MapImage[] image = renderService.GetMap(mapSpec);
   map = new SqlBinary(image[0].MimeData.Bits);
}

部署和调试 GetProximity:

部署使用 Web 服务的表值函数比前一示例要更为复杂。部署使用 Web 服务的表值函数的全部步骤如下:

1.将包含 GetProximity 函数的项目配置为预先生成 XML 序列化程序集。当 .NET Framework 调用 Web 服务时,它会动态生成一个程序集来处理 SOAP XML 的序列化和反序列化。这就出现了一个问题,因为 SQL Server CLR 主机不允许在运行时动态加载程序集。因此,必须在编译时生成 Web 服务调用的 XML 序列化程序集,并向 SQL Server 注册它。若要预先从 Visual Studio 生成此程序集,请从“项目”菜单中,单击“属性”并选择“生成”。将“生成序列化程序集”设置为“开启”。XML 序列化 DLL 随即会随您的项目生成并被添加到 Bin 目录中。其名称为 [ProjectName].XmlSerializers.dll。

2.将 System.Security.AllowPartiallyTrustedCallers 属性添加到该程序集中。此操作可通过在该项目的 AssemblyInfo.cs 中添加以下行来完成:

[assembly: System.Security.AllowPartiallyTrustedCallers]

这样,XML 序列化程序集就可以和包含 GetProximity 函数的主程序集通信。

3.向 SQL Server 注册在步骤 1 中创建的 XML 序列化 DLL。SAFE 权限集就足够了。

4.为包含 GetProximity 表值函数的 DLL 创建一个非对称密钥。

5.为该非对称密钥创建登录信息,并授予它 EXTERNAL ACCESS 权限集。

6.使用 EXTERNAL ACCESS 权限集注册包含 GetProximity 的程序集。

7.注册表值函数 GetProximity。

由于这个依赖关系链相对而言更为冗长和复杂,所以我放弃了 Visual Studio 的部署机制,而选择了作为生成后步骤运行的 Transact-SQL 脚本,它执行部署步骤 3-7。示例项目中包含了该脚本。

调试表值函数非常简单。数据库项目有一个 Test Scripts 目录。您可以将脚本添加到该目录中并直接从 Visual Studio 运行它们。成功部署了该函数后,您可以创建一个调用该函数的 Transact-SQL 查询,并在不离开 Visual Studio 的情况下执行完该函数的 C# 代码。

若要测试 GetProximity,请在 Test Scripts 目录中创建一个名为“Test.sql”的测试脚本,并在该文件中添加以下查询:

SELECT * FROM GetProximity('Redmond', 'WA', 5, 'SIC3578')

请注意函数的参数。我将我的临近区查询集中在华盛顿州的雷蒙德市,因此我将“Redmond”用于 @city 参数,将“WA”用于 @state 参数。我为 @count 值提供了数字 5,这是我希望返回的实体数。我还为 @entityTypeName 参数提供了值“SIC3578”,这是我使用的 MapPoint 数据源中的 ATM 的实体名。有关 MapPoint 数据源和实体类型的详细信息,请参阅 MapPoint 数据源。

若要在 Visual Studio 中运行该查询,请右键单击解决方案资源管理器中的 Test.sql 文件并选择“调试脚本”。您将在 Visual Studio 输出窗口中得到与以下内容类似的结果:

HitName             HitAddress                          MapImage
----------------------------------------------------------------------
Woodgrove Bank      8502 160th Ave NE Redmond WA        <BINARY>
Woodgrove Bank      16025 NE 85th St Redmond WA         <BINARY>
Woodgrove Bank      16150 NE 85th St Redmond WA         <BINARY>
Woodgrove Bank      8867 161st Ave NE Redmond WA        <BINARY>
Woodgrove Bank      15600 Redmond Way Redmond WA        <BINARY>
No rows affected.
(5 row(s) returned)

若要调试 GetProximity 函数,请在 C# 代码中为该函数设置一个断点,并再次运行该脚本。在指定的点,执行将中止,您可以像对任何其他托管过程一样对它进行调试。

使用 MapPoint Web 服务创建报表:

随 SQL Server 2005 附带的 AdventureWorks 示例数据库描述了虚构的自行车和自行车配件制造商,该厂商向全美国的零售店出售产品。在本示例中,Adventure Works Cycles 决定停止接受信用卡或支票。从现在起,他们希望所有发票都只以现金支付。在此过渡期间,为了方便其客户,该厂商正在制作一份报表,在其中显示距离其客户的零售店位置最近的五台 ATM 的地址和地图。这不是一个现实场景,但可以起到说明如何使用表值函数将传统数据源(SQL 数据库)与非传统数据源(MapPoint Web 服务)联接在一起的目的。

创建我们的报表的第一步是在 Visual Studio 中创建一个新的 Report Server 项目,并指定数据源。我的报表的数据源是 SQL Server 2005 AdventureWorks 示例数据库,其中有我以前创建的 MapPoint 表值函数。有一个用于该报表的数据集。它包含零售店名、零售店所在的城市、州、ATM 名、ATM 地址和 ATM 路线图等字段。

对于每个零售店,我们都希望调用 GetProximity 并找出五台最近的 ATM。在 SQL Server 2005 中,有一个新的 APPLY 子句类型可处理此问题。这与联接稍有不同,因为我们希望联接函数参数,而不是函数结果。这意味着表值函数是为 APPLY 左侧返回的每一行调用的。然后,就可以将函数结果的并集联接到该查询的其余部分。以下是该报表数据集的 Transact-SQL 查询:

SELECT TOP(40) Sales.Store.Name, Person.Address.City,
   Person.StateProvince.StateProvinceCode, GetProximity_1.HitName,
   GetProximity_1.HitAddress, GetProximity_1.MapImage
   FROM Sales.CustomerAddress
      INNER JOIN Person.Address
         ON Sales.CustomerAddress.AddressID = Person.Address.AddressID
         AND Sales.CustomerAddress.AddressID = Person.Address.AddressID
      INNER JOIN Sales.Store
      INNER JOIN Sales.StoreContact ON Sales.Store.CustomerID = 
Sales.StoreContact.CustomerID
         ON Sales.CustomerAddress.CustomerID = 
Sales.StoreContact.CustomerID
      INNER JOIN Person.StateProvince ON Person.Address.StateProvinceID 
= Person.StateProvince.StateProvinceID
         AND Person.Address.StateProvinceID = 
Person.StateProvince.StateProvinceID
CROSS APPLY dbo.GetProximity(Person.Address.City,
   Person.StateProvince.StateProvinceCode, 5, 'SIC3578') AS 
GetProximity_1

请注意,其中使用了 CROSS APPLY 来将 GetProximity 函数参数与 Person.Address.City 和 Person.StateProvince.StateProvinceCode 表示的其他查询数据进行链接。

注意 若要在数据集查询中使用 APPLY 子句,必须使用通用查询设计器。基于 GUI 的查询设计器不能以图形方式显示它,并会引发异常。

我的报表设计使用两个嵌套的列表。内部列表包含用于 ATM 名称和地址的文本框,以及地图的图像。在我的报表中,此图像设置为 AutoSize,因此它会根据 Web 服务中的图像的大小而调整。外部列表包含用于零售店的名称和位置的文本框。外部列表按零售店名称进行分组。我的报表在布局模式下的图像如图 3 所示。

图 3. 布局模式下的报表图像

图 4 显示绘制有 ATM 位置图的报表

结论

本白皮书说明了如何使用 SQL Server 中的表值函数来扩展 SQL Server Reporting Services 的数据访问功能。表值函数让编程人员和报表设计员有一定的灵活性,使他们能够实现数据不直接存储在数据库表中的报表制作场景。

上一页 [1] [2] 下一页

关于本站 | 帮 助 | 广告服务 | 版权声明 | 业务合作 | 捐助本站 | 软件发布 | 联系我们
77资源下载 www.77zy.com ©2007-2008 版权所有
备案编号:赣ICP备07002641号  QQ:674648476