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. BestCoder Round #1 第二题 项目管理

    // 第二题 我记得很久很久很久以前看过这样的题目,忘记是哪的区域赛了 // 记得有人说和节点度数有关,我记不清了,反正当时完全不懂 // 然后我想了想,估计就是更新节点度数有关,YY出来可能只要更新 ...

  2. pyqtree

    pyqtree module API Documentation Classes class Index The top spatial index to be created by the user ...

  3. Youtube 视频下载

    Youtube 视频下载 由于特殊原因,需要下载 Youtube 的视频. https://www.clipconverter.cc/

  4. qt ui程序使用Linux的文件操作open、close (转)

    原文地址:qt ui程序使用Linux的文件操作open.close 作者:kjpioo 提出这个问题是因为在qt的QWidget类型的对象中,close()函数会和QWidget::close()冲 ...

  5. git仓库

    关于仓库,我们先搞清楚三个概念:本地仓库.远程仓库和上游仓库.本地仓库是从远程仓库clone出来的,远程仓库可以从上游仓库fork出来.这里的clone和fork都是复制的意思,区别是本地和远程都是针 ...

  6. java web 程序---刷新页面次数

    <%! int count=0; %> <% count++; session.setAttribute("count",count); out.print(&q ...

  7. 虚拟机桥接网卡下配置centOS静态IP

    前面我们讲了怎么去配置asterisk,但是配置完了,是没有什么效果出现的,因为asterisk相当于一个服务器,我们需要一个客户端去给它连接起来,如果你是在自己的机子上装了虚拟机,那最好配一下cen ...

  8. Joker的运维开发之路

    python 1--数据类型,流程控制 2--数据类型详细操作,文件操作,字符编码 https://mp.weixin.qq.com/s/i3lcIP82HdsSr9LzPgkqww 点开更精彩 目前 ...

  9. ALSA声卡16_编写ALSA声卡应用程序_学习笔记

    1.体验 (1)ALSA声卡使用体验:使用arecord录音,使用aplay播放,在Alsa-utils里面) 准备: cd linux-3.4.2 patch -p1 < ../linux-3 ...

  10. 5月8日上课笔记-浮动float

    IO文件复制 字符流(只能对文本文件进行操作) Reader Writer 字节流(对所有文件都能操作) InputStream OutputStream 一.浮动 边框弧度 border-radiu ...