SData:优雅的数据交换方案
SData的网址是https://github.com/knat/SData。
数据交换方案可以分为两类:有纲要(schema)的和无纲要的。有纲要的数据交换方案有Google的Protocol Buffers,Microsoft的Bond以及SData,纲要编译器在编译时刻把纲要与编程语言进行映射,也就是通过纲要生成编程语言代码,此类方案是静态类型化的。无纲要的数据交换方案有JSON(我知道存在JSON schema,但它并不在编译阶段起作用),序列化器(serializer)在运行时刻把数据与编程语言进行映射,此类方案是动态类型化的。静态类型化的数据交换方案的优点是类型安全和高性能,缺点是不够灵活,这和静态类型化的编程语言与动态类型化的编程语言的差异类似。
SData的纲要语言优雅强大,面向对象,类型丰富,代码生成机制优美灵活。下面是通过示例来介绍SData。
1)你需要Visual Studio 2015。
2)下载并安装最新的SData VSIX package(SData-*.vsix)。
3)打开VS 2015,新建一个Console Application,卸载项目并编辑csproj文件,将下面的代码插入到文件末尾:
<!--Begin SData-->
<Import Project="$([System.IO.Directory]::GetFiles($([System.IO.Path]::Combine($([System.Environment]::GetFolderPath(SpecialFolder.LocalApplicationData)), `Microsoft\VisualStudio\14.0\Extensions`)), `SData.targets`, System.IO.SearchOption.AllDirectories))" />
<!--End SData-->
4)加载项目,打开"Add New Item"对话框 -> Visual C# Items -> SData, 新建一个SData纲要文件,将下面的代码拷贝到该文件中:
//Biz.sds。SData纲要文件的扩展名是sds
namespace "http://example.com/business"//名称空间由URI标识
{
class Person abstract/*抽象类不能拥有实例*/ key Id//指定某属性为类的键,该属性值必须唯一
{
Id/*属性名*/ as Int32//属性类型
Name as String
Phones as list<String>//list是个有序集合
RegDate as nullable<DateTimeOffset>//可空类型可以接受null值
}
class Customer extends Person//继承
{
//每个属性在类中必须拥有唯一的名字
Reputation as Reputation
Orders as nullable<set<Order>>//set是个无序集合,每个条目必须唯一,即Order.Id必须唯一
}
enum Reputation as Int32//枚举的underlying类型
{
None = 0
Bronze = 1
Silver = 2
Gold = 3
Bad = -1
}
class Order key Id
{
Id as Int64
Amount as Decimal
IsUrgent as Boolean
}
class Supplier extends Person
{
BankAccount as String
Products as map<Int32/*key*/, String/*value*/>//map是个无序的key-value集合,每个key必须唯一
}
}
namespace "http://example.com/business/api"
{
//要引用另一个名称空间的成员,需使用import指令
import "http://example.com/business"/*名称空间URI*/ as biz/*为URI取个别名,可选*/
class DataSet
{
People as set<Person>
ETag as Binary
}
}
编译项目时,SData纲要编译器会检查纲要文件的正确性:
5)将SData runtime library NuGet package添加到项目中:
PM> Install-Package SData -Pre
6)在C#文件中,使用SData.SchemaNamespaceAttribute
特性指定纲要名称空间到C#名称空间的映射:
//Program.cs
using SData;
[assembly: SchemaNamespace("http://example.com/business"/*纲要名称空间URI*/,
"Example.Business"/*C#名称空间名字*/)]
//所有的纲要名称空间必须被映射
[assembly: SchemaNamespace("http://example.com/business/api", "Example.Business.API")]
编译项目时,SData纲要编译器在检查了纲要文件的正确性后,会解析C#文件,并在__SDataGenerated.cs文件中生成代码,打开并查看该文件。
7)使用生成的代码非常简单,将下面的代码拷贝到Program.cs中:
//Program.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using SData;
using Example.Business;
using Example.Business.API;
[assembly: SchemaNamespace("http://example.com/business", "Example.Business")]
[assembly: SchemaNamespace("http://example.com/business/api", "Example.Business.API")]
class Program
{
static void Main()
{
//重要:在程序初始化时调用SData_Assembly_Name.Initialize()以初始化元数据
SData_ConsoleApplication1.Initialize();
var ds = new DataSet
{
People = new HashSet<Person>
{
new Customer
{
Id = 1, Name = "Tank", RegDate = DateTimeOffset.Now,
Phones = new List<string> { "1234567", "2345678"},
Reputation = Reputation.Bronze,
Orders = new HashSet<Order>
{
new Order { Id = 1, Amount = 436.99M, IsUrgent = true},
new Order { Id = 2, Amount = 98.77M, IsUrgent = false},
}
},
new Customer
{
Id = 2, Name = "Mike",
Phones = new List<string>(),
Reputation = Reputation.Gold,
},
new Supplier
{
Id = 3, Name = "Eric", RegDate = DateTimeOffset.UtcNow,
Phones = new List<string> {"7654321" },
BankAccount="11223344", Products = new Dictionary<int, string>
{
{ 1, "Mountain Bike" },
{ 2, "Road Bike" },
}
}
},
ETag = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 },
};
using (var writer = new StreamWriter("DataSet.txt"))
{
ds.Save(writer, " ", "\r\n");
}
DataSet result;
var context = new LoadingContext();
using (var reader = new StreamReader("DataSet.txt"))
{
if (!DataSet.TryLoad("**DataSet.txt**", //filePath只是个标识符
reader, context, out result))
{
foreach (var diag in context.DiagnosticList)
{
Console.WriteLine(diag.ToString());
}
Debug.Assert(false);
}
}
}
}
8)打开并查看DataSet.txt(注释是我添加的):
//SData数据格式示例
//一个数据文件必须包含且仅包含一个类数据
<a0/*alias*/ = @"http://example.com/business"/*URI*/> {//类DataSet的数据
People = [//list或set类型的数据
//因为类Person是抽象的,类型指示器'(alias::className)'用来指定数据的类类型
(a0::Customer) {
Id = 1,
Name = @"Tank",
Phones = [
@"1234567",
@"2345678",
],
RegDate = "2015-07-31T11:19:26.7854059+08:00",
Reputation = a0::Reputation.Bronze,
Orders = [
{
Id = 1,
Amount = 436.99,
IsUrgent = true,
},
{
Id = 2,
Amount = 98.77,
IsUrgent = false,
},
],
},
(a0::Customer) {
Id = 2,
Name = @"Mike",
Phones = [
],
Reputation = a0::Reputation.Gold,
//如果某属性的类型是nullable,则该属性可以缺失,否则必须出现
//允许未知的属性
},
(a0::Supplier) {
Id = 3,
Name = @"Eric",
Phones = [
@"7654321",
],
RegDate = "2015-07-31T03:19:26.8010317+00:00",
BankAccount = @"11223344",
Products = #[//map类型的数据
1 = @"Mountain Bike",
2 = @"Road Bike",
],
},
],
ETag = "AQIDBAUGBwg=",
}
9)在行var context = new LoadingContext();
设置一个断点,当程序运行到断点时,打开修改保存DataSet.txt文件,比如删除行Name = @"Tank",
,因为属性Name
的类型不是nullable
,即该属性是必须的,TryLoad()
将会失败,下面的诊断信息将打印在控制台:
Error -293: Property 'Name' missing.
**DataSet.txt**: (23,9)-(23,9)
10)因为每个生成的C#类都标注了partial
修饰符,自定义代码可以添加到C#类中:
//my.cs
namespace Example.Business
{
partial class Person : SomeClass, ISomeInterface
{
public int MyProperty { get; set; }
public abstract void MyMethod();
}
partial class Customer
{
//注意:非抽象类必须有无参构造方法
public override void MyMethod() { }
}
}
11)可以添加自定义验证:
//my.cs
using System;
using SData;
public class MyLoadingContext : LoadingContext
{
public bool CheckCustomerReputation { get; set; }
public override void Reset()
{
base.Reset();
//...
}
}
public enum MyDiagnosticCode
{
PhonesIsEmpty = 1,
BadReputationCustomer,
}
namespace Example.Business
{
partial class Person
{
//OnLoading() is called by the serializer just after the object is created
private bool OnLoading(LoadingContext context, TextSpan textSpan)
{
Console.WriteLine("Person.OnLoading()");
return true;
}
//OnLoaded() is called just after all properties are set
private bool OnLoaded(LoadingContext context, TextSpan textSpan)
{
Console.WriteLine("Person.OnLoaded()");
if (Phones.Count == 0)
{
context.AddDiagnostic(DiagnosticSeverity.Error,
(int)MyDiagnosticCode.PhonesIsEmpty, "Phones is empty.", textSpan);
return false;
}
return true;
//if error diagnostics are added to the context, the method must return false.
//if any OnLoading() or OnLoaded() returns false, TryLoad() will return false immediately
}
}
partial class Customer
{
//the serializer will call base method(Person.OnLoading()) first
private bool OnLoading(LoadingContext context, TextSpan textSpan)
{
Console.WriteLine("Customer.OnLoading()");
return true;
}
//the serializer will call base method(Person.OnLoaded()) first
private bool OnLoaded(LoadingContext context, TextSpan textSpan)
{
Console.WriteLine("Customer.OnLoaded()");
var myContext = (MyLoadingContext)context;
if (myContext.CheckCustomerReputation && Reputation == Business.Reputation.Bad)
{
context.AddDiagnostic(DiagnosticSeverity.Warning,
(int)MyDiagnosticCode.BadReputationCustomer, "Bad reputation customer.",
textSpan);
//if non-error diagnostics are added to the context, the method should return true.
}
return true;
}
}
}
//Program.cs
//...
var context = new MyLoadingContext() { CheckCustomerReputation = true };
using (var reader = new StreamReader("DataSet.txt"))
{
if (!DataSet.TryLoad("**DataSet.txt**", reader, context, out result))
//...
12)使用SData.SchemaClassAttribute
特性将纲要类显式映射到C#类,使用SData.SchemaPropertyAttribute
特性将纲要属性显式映射到C#属性/域,SData纲要编译器会解析这些特性:
//my.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using SData;
namespace Example.Business
{
[SchemaClass("Person"/*schema class name*/)]
partial class Contact
{
[SchemaProperty("RegDate"/*schema property name*/)]
public DateTimeOffset? RegistrationDate { get; internal set; }
//same-named schema property and C# property/field are mapped implicitly
public string Name { get; internal set; }
[SchemaProperty("Phones")]
private Collection<string> _phones;
public Collection<string> Phones
{
get { return _phones ?? (_phones = new Collection<string>()); }
}
//list<T> can be mapped to System.Collections.Generic.ICollection<T> or implementing class
//set<T> can be mapped to System.Collections.Generic.ISet<T> or implementing class
//map<TKey, TValue> can be mapped to System.Collections.Generic.IDictionary<TKey, TValue>
// or implementing class
}
//same-named schema class and C# class are mapped implicitly
partial class Supplier
{
public IDictionary<int, string> Products { get; internal set; }
}
}
namespace Example.Business.API
{
partial class DataSet
{
[SchemaProperty("People")]
public ISet<Contact> Contacts { get; set; }
}
}
SData.SchemaNamespaceAttribute
,SData.SchemaClassAttribute
和SData.SchemaPropertyAttribute
是编译时特性,它们与运行时无关,这算是元编程。
更多信息请访问https://github.com/knat/SData。
SData:优雅的数据交换方案的更多相关文章
- jQuery数据缓存方案详解:$.data()的使用
我们经常使用隐藏控件或者是js全局变量来临时存储数据,全局变量容易导致命名污染,隐藏控件导致经常读写dom浪费性能.jQuery提供了自己的数据缓存方案,能够达到和隐藏控件.全局变量相同的效果,但是j ...
- Android Learning:数据存储方案归纳与总结
前言 最近在学习<第一行android代码>和<疯狂android讲义>,我的感触是Android应用的本质其实就是数据的处理,包括数据的接收,存储,处理以及显示,我想针对这几 ...
- 常用两种数据交换格式之XML和JSON的比较
目前,在web开发领域,主要的数据交换格式有XML和JSON,对于XML相信每一个web developer都不会感到陌生: 相比之下,JSON可能对于一些新步入开发领域的新手会感到有些陌生,也可能你 ...
- XML和JSON两种数据交换格式的比较
在web开发领域,主要的数据交换格式有XML和JSON,对于在 Ajax开发中,是选择XML还是JSON,一直存在着争议,个人还是比较倾向于JSON的.一般都输出Json不输出xml,原因就是因为 x ...
- Disruptor——一种可替代有界队列完成并发线程间数据交换的高性能解决方案
本文翻译自LMAX关于Disruptor的论文,同时加上一些自己的理解和标注.Disruptor是一个高效的线程间交换数据的基础组件,它使用栅栏(barrier)+序号(Sequencing)机制协调 ...
- 从Exchager数据交换到基于trade-off的系统设计
可以使用JDK提供的Exchager类进行同步交换:进行数据交换的双方将互相等待对方,直到双方的数据都准备完毕,才进行交换.Exchager类很少用到,但理解数据交换的时机却十分重要,这是一个基于tr ...
- DataStage 九、数据交换到MySQL以及乱码问题
DataStage序列文章 DataStage 一.安装 DataStage 二.InfoSphere Information Server进程的启动和停止 DataStage 三.配置ODBC Da ...
- 数据交换格式XML和JSON对比
1.简介: XML:extensible markup language,一种类似于HTML的语言,他没有预先定义的标签,使用DTD(document type definition)文档类型定义来组 ...
- MES系统与喷涂设备软件基于文本文件的数据对接方案
产品在生产过程中除了记录产品本身的一些数据信息,往往还需要记录下生产设备的一些参数和状态,这也是MES系统的一个重要功能.客户的药物支架产品,需要用到微量药物喷涂设备,客户需要MES系统能完整记录下每 ...
随机推荐
- 使用stsadm.exe工具实现SharePoint网站备份还原
一.过程描述: 首先在源站点机器上用stsadm.exe备份网站集,讲备份文件拷贝到目标服务器(也可直接在备份时配置备份路径为目标机器路径),然后执行还原操作:首先新建网站集,然后用SharePoin ...
- L2-017. 人以群分
社交网络中我们给每个人定义了一个“活跃度”,现希望根据这个指标把人群分为两大类,即外向型(outgoing,即活跃度高的)和内向型(introverted,即活跃度低的).要求两类人群的规模尽可能接近 ...
- CentOS 6.6下安装OpenOffice4.0
最近由于项目需要,要在公司服务器上安装Openoffice,网上搜了一些资料后成功安装,现分享给大家. 1.首先先下载好需要的rpm包:Apache_OpenOffice_4.0.0_Linux_x8 ...
- Tomcat 运行 idea 编译好的 .class JavaWeb 项目
对于新手来说,对于项目部署,有时候就是以为拷贝在idea控制台里面跑的项目放到tomcat里面的webapps里面跑就可以了,这仅仅限于静态项目..... 他不像PHP , 修改源码直接可以跑, 而J ...
- 借助CustomBehaviorsLibrary.dll写出水印效果(转)
在项目中载入这个dll 之后引用 使用方法具体如下图: 在这里需要注意到是项目中对interactivity的引用 : 好文要顶 关注我 收藏该文
- webpack 简单使用
备注: 使用yarn 结合npm 模块进行简单项目开发 1. 安装 yarn init yarn add webpack --dev yarn global add live-server 2. 添 ...
- Unit05: WEB项目的开发模式 、转发 和 Unit09: EL、JSTL
Unit05: WEB项目的开发模式 .转发 和 Unit09: EL.JSTL dao package dao; import java.io.Serializable; import jav ...
- golang的最简单的文件浏览web服务器
网上看到的,记录下,备用 package main import ( "net/http" ) func main() { http.Handle("/", h ...
- RandomStringUtils工具类(java随机生成字符串)
使用RandomStringUtils可以选择生成随机字符串,可以是全字母,全数字,自定义生成字符等等... 其最基础的方法: 参数解读: count:需要生成的随机串位数 letters:只要字母 ...
- appium+python自动化37-adb shell模拟点击事件(input tap)
前言 appium有时候定位一个元素很难定位到,或者说明明定位到这个元素了,却无法点击,这个时候该怎么办呢? 求助大神是没用的,点击不了就是点击不了,appium不是万能的,这个时候应该转换思路,换其 ...