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.SchemaNamespaceAttributeSData.SchemaClassAttributeSData.SchemaPropertyAttribute是编译时特性,它们与运行时无关,这算是元编程。

更多信息请访问https://github.com/knat/SData

SData:优雅的数据交换方案的更多相关文章

  1. jQuery数据缓存方案详解:$.data()的使用

    我们经常使用隐藏控件或者是js全局变量来临时存储数据,全局变量容易导致命名污染,隐藏控件导致经常读写dom浪费性能.jQuery提供了自己的数据缓存方案,能够达到和隐藏控件.全局变量相同的效果,但是j ...

  2. Android Learning:数据存储方案归纳与总结

    前言 最近在学习<第一行android代码>和<疯狂android讲义>,我的感触是Android应用的本质其实就是数据的处理,包括数据的接收,存储,处理以及显示,我想针对这几 ...

  3. 常用两种数据交换格式之XML和JSON的比较

    目前,在web开发领域,主要的数据交换格式有XML和JSON,对于XML相信每一个web developer都不会感到陌生: 相比之下,JSON可能对于一些新步入开发领域的新手会感到有些陌生,也可能你 ...

  4. XML和JSON两种数据交换格式的比较

    在web开发领域,主要的数据交换格式有XML和JSON,对于在 Ajax开发中,是选择XML还是JSON,一直存在着争议,个人还是比较倾向于JSON的.一般都输出Json不输出xml,原因就是因为 x ...

  5. Disruptor——一种可替代有界队列完成并发线程间数据交换的高性能解决方案

    本文翻译自LMAX关于Disruptor的论文,同时加上一些自己的理解和标注.Disruptor是一个高效的线程间交换数据的基础组件,它使用栅栏(barrier)+序号(Sequencing)机制协调 ...

  6. 从Exchager数据交换到基于trade-off的系统设计

    可以使用JDK提供的Exchager类进行同步交换:进行数据交换的双方将互相等待对方,直到双方的数据都准备完毕,才进行交换.Exchager类很少用到,但理解数据交换的时机却十分重要,这是一个基于tr ...

  7. DataStage 九、数据交换到MySQL以及乱码问题

    DataStage序列文章 DataStage 一.安装 DataStage 二.InfoSphere Information Server进程的启动和停止 DataStage 三.配置ODBC Da ...

  8. 数据交换格式XML和JSON对比

    1.简介: XML:extensible markup language,一种类似于HTML的语言,他没有预先定义的标签,使用DTD(document type definition)文档类型定义来组 ...

  9. MES系统与喷涂设备软件基于文本文件的数据对接方案

    产品在生产过程中除了记录产品本身的一些数据信息,往往还需要记录下生产设备的一些参数和状态,这也是MES系统的一个重要功能.客户的药物支架产品,需要用到微量药物喷涂设备,客户需要MES系统能完整记录下每 ...

随机推荐

  1. 使用stsadm.exe工具实现SharePoint网站备份还原

    一.过程描述: 首先在源站点机器上用stsadm.exe备份网站集,讲备份文件拷贝到目标服务器(也可直接在备份时配置备份路径为目标机器路径),然后执行还原操作:首先新建网站集,然后用SharePoin ...

  2. L2-017. 人以群分

    社交网络中我们给每个人定义了一个“活跃度”,现希望根据这个指标把人群分为两大类,即外向型(outgoing,即活跃度高的)和内向型(introverted,即活跃度低的).要求两类人群的规模尽可能接近 ...

  3. CentOS 6.6下安装OpenOffice4.0

    最近由于项目需要,要在公司服务器上安装Openoffice,网上搜了一些资料后成功安装,现分享给大家. 1.首先先下载好需要的rpm包:Apache_OpenOffice_4.0.0_Linux_x8 ...

  4. Tomcat 运行 idea 编译好的 .class JavaWeb 项目

    对于新手来说,对于项目部署,有时候就是以为拷贝在idea控制台里面跑的项目放到tomcat里面的webapps里面跑就可以了,这仅仅限于静态项目..... 他不像PHP , 修改源码直接可以跑, 而J ...

  5. 借助CustomBehaviorsLibrary.dll写出水印效果(转)

    在项目中载入这个dll 之后引用 使用方法具体如下图: 在这里需要注意到是项目中对interactivity的引用 :       好文要顶 关注我 收藏该文  

  6. webpack 简单使用

    备注:  使用yarn 结合npm 模块进行简单项目开发 1. 安装 yarn init yarn add webpack --dev yarn global add live-server 2. 添 ...

  7. Unit05: WEB项目的开发模式 、转发 和 Unit09: EL、JSTL

    Unit05: WEB项目的开发模式 .转发   和  Unit09: EL.JSTL dao package dao; import java.io.Serializable; import jav ...

  8. golang的最简单的文件浏览web服务器

    网上看到的,记录下,备用 package main import ( "net/http" ) func main() { http.Handle("/", h ...

  9. RandomStringUtils工具类(java随机生成字符串)

    使用RandomStringUtils可以选择生成随机字符串,可以是全字母,全数字,自定义生成字符等等... 其最基础的方法: 参数解读: count:需要生成的随机串位数 letters:只要字母 ...

  10. appium+python自动化37-adb shell模拟点击事件(input tap)

    前言 appium有时候定位一个元素很难定位到,或者说明明定位到这个元素了,却无法点击,这个时候该怎么办呢? 求助大神是没用的,点击不了就是点击不了,appium不是万能的,这个时候应该转换思路,换其 ...