OData开放数据协议是微软针对Google的GData推出的,旨在推广Web程序数据库格式标准化的开放数据协议,微软将 OData 定义为基于 HTTP、AtomPub 和 JSON 的协议,增强各种网页应用程序之间的数据兼容性,以提供多种应用、服务和数据商店的信息访问。并且,微软已经正式推出了 OData SDK,包含了 .NET、Java、PHP、Palm WebOS 和 iPhone 的支持。其中 .Net OData 客户端基于 Apache 授权开源。微软多款产品已经支持 OData 包括 SharePoint Server 2010, Excel 2010, Dynamics 等。
微软第一代数据交换协议叫ODBC(开放数据库联接 Open Database Connectivity),目前仍旧可见于window和linux的原生程序开发之中,其目的是为操作系统下的应用程序之间提供统一的数据交互的API,是函数式的。之后,微软推出了第二代:OLE DB,带来了OOP式样的交互API,以及跨网络的数据交互的可能性(通过DCOM),OLE DB 标准的具体实现是一组C++ API 函数,就像ODBC 标准中的ODBC API 一样,不同的是,OLE DB 的API 是符合COM 标准、基于对象的(ODBC API 则是简单的C API)。使用OLE DB API,可以编写能够访问符合OLE DB 标准的任何数据源的应用程序,也可以编写针对某种特定数据存储的查询处理程序(Query Processor)和游标引擎(Cursor Engine),因此OLE DB 标准实际上是规定了数据使用者和提供者之间的一种应用层的协议(Application-Level Protocol)。在云计算时代,web应用已经是主流,程序主要通过HTTP Request来表达需求,通过HTTP Response来获取结果,ODBC和OLE DB都已无法使用。微软于是开发了其第三代数据交互协议:OData开放数据协议。
在SOA的世界中,最重要的一个概念就是契约(contract)。在云计算的世界中,有关通信的最重要的概念也是契约。XML具有强大对数据的描述能力,Atom格式和AtomPub都建立在XML之上,在Google和微软的推动下,也已经成为标准。但是,Atom/AtomPub和ODBC/OLEDB这样的真正数据交互协议相比较,还有着根本上的欠缺:缺乏数据类型的具体描述,降低了交互性能。缺乏对数据查询的控制能力,比如返回特定的数据集合的区间,或者说分页能力等等。微软基于EDM模型释出了:OData,这里也可以看出Entity Framework对于NHibernate这样的ORM的工具不同的战略考虑。
在PDC大会上,微软宣布了一个代号为 “Dallas”的社区技术预览(CTP),由Windows Azure 和SQL Azure构建的信息服务,能够让开发者与信息工作者在任何平台上使用优质的第三方数据集和内容。“Dallas”也可以通过使用微软技术自助的商务智能与分析存储的数据集。Dallas所使用的数据交互协议就是OData。
在微软的解决方案中,是用WCF来处理所有程序间的通信,针对数据通信,WCF Data Services自然是最好的选择。首先,WCF Data Services是WCF服务,所以你可以使用所有现有的WCF知识。其次,WCF Data Services已经实现了OData拓扑,于是你可以致力于你的数据格式在你的程序中的表示,而不是AtomPub/JSON这些真正在网络上传递的数据格式。再有,WCF Data Services致力于数据传输,而不是数据存储。你的数据可以存放在任何位置:本地的数据库,云端的数据库,外部的web services,xml文件,等等。无论数据是怎么来的,你都可以用同样的方式来发布/使用它们。
下面我们就使用WCF Data Service将服务器的Windows应用程序日志向外发布。我们的应用程序可以把日志直接就写在Windows的日志里,然后通过使用WCF Data Service非常容易的就将日志想其他需要的用户公开。WCF Data Service默认使用的是Entity Framework,使用Entity Framework参看文章WCF Data Service QuickStart,还有一个Reflection Provider,可以支持只读的数据服务,这个例子就是演示使用Reflection Provider,资料参看MSDN:http://msdn.microsoft.com/en-us/library/dd723653(VS.100).aspx,还可以自定义实现一个Provider,参看文章自定义Data Service Providers。
首先定义一个Windows日志的实体,类似于WCF的DataContract,这里使用的是EDM的映射:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Services.Common;
namespace ReflectionDataServiceDemo
{
[EntityPropertyMappingAttribute("Source",
SyndicationItemProperty.Title,
SyndicationTextContentKind.Plaintext, true)]
[EntityPropertyMapping("Message",
SyndicationItemProperty.Summary,
SyndicationTextContentKind.Plaintext, true)]
[EntityPropertyMapping("TimeGenerated",
SyndicationItemProperty.Updated,
SyndicationTextContentKind.Plaintext, true)]
[DataServiceKey("EventID")]
public class LogEntry
{
public long EventID
{
get;
set;
}
public string Category
{
get;
set;
}
public string Message
{
get;
set;
}
public DateTime TimeGenerated
{
get;
set;
}
public string Source
{
get;
set;
}
}
}
上面使用一个新特性Friendly feeds 将数据映射到标准ATOM元素,其中DataServiceKey是唯一一个必须的标记,然后使用Reflection Provider实现一个IQueryable接口的数据源:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Diagnostics;
namespace ReflectionDataServiceDemo
{
public class LogDataSource
{
string source;
public LogDataSource(string source)
{
this.source = source;
}
public LogDataSource()
{
}
public IQueryable<LogEntry> LogEntries
{
get { return GetEntries().AsQueryable().OrderBy(e => e.TimeGenerated); }
}
private IEnumerable<LogEntry> GetEntries()
{
var applicationLog = System.Diagnostics.EventLog.GetEventLogs().Where(e => e.Log == "Application")
.FirstOrDefault();
var entries = new List<LogEntry>();
if (applicationLog != null)
{
foreach (EventLogEntry entry in applicationLog.Entries)
{
if (source == null || entry.Source.Equals(source, StringComparison.InvariantCultureIgnoreCase))
{
entries.Add(new LogEntry
{
Category = entry.Category,
EventID = entry.InstanceId,
Message = entry.Message,
TimeGenerated = entry.TimeGenerated,
Source = entry.Source,
});
}
}
}
return entries.OrderByDescending(e => e.TimeGenerated)
.Take(200);
}
}
}
最后添加一个WCF Data Service,代码非常的简单,主要的工作就是将上述数据源通过WCF Data Service发布出去:
using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
using System.Configuration;
namespace ReflectionDataServiceDemo
{
public class LogDataService : DataService<LogDataSource >
{
// 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("*", EntitySetRights.AllRead);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
protected override LogDataSource CreateDataSource()
{
string source = ConfigurationManager.AppSettings["EventLogSource"];
if (source == null)
{
throw new ApplicationException("The EventLogSource appsetting is missing in the configuration file");
}
return new LogDataSource(source);
}
}
}
我们再来写个简单控制台客户端消费这个Service,通过Visual Studio的添加服务引用生成服务的客户端代码,还可以使用一个插件Open Data Protocol Visualizer查看服务返回的OData数据数据,这个工具的获取和安装可以参看VS2010的扩展。可以通过服务引用的“View in Diagram”进行查看。
客户端的代码也非常简单;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ClientDemo
{
class Program
{
static void Main(string[] args)
{
LogDataService.LogDataSource logSource = new LogDataService.LogDataSource(new Uri("http://localhost:3399/LogDataService.svc/"));
foreach (var item in logSource.LogEntries)
{
Console.WriteLine(string.Format("来自{0}事件{1},内容{2}", item.Source, item.EventID, item.Message));
}
Console.Read();
}
}
}
项目代码可以到这里获取 http://cid-33478a966734670f.skydrive.live.com/self.aspx/.Public/ReflectionDataServiceDemo.zip