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系统能完整记录下每 ...
随机推荐
- SQL Server 为存储过程添加预定设置注释代码
一个优秀的项目最少不了的是代码注释,兴许你是代码高手入目既知道该段代码主要功能是什么,但日子长了,记的东西多了,即时再熟悉的代码也渐渐的有点不认 识它,所以养成良好的写注释的习惯,对于自己对于他人都是 ...
- 安装WampServer关闭mysql服务后打不开了
WampServer自带了mysql精简班的数据库了 WampServer自带 的mysql和你独立安装的mysql端口号冲突了 mysql默认的端口号是3306 建议你修改WampServer的数据 ...
- Tomcat最大连接数问题
Tomcat的server.xml中Context元素的以下参数应该怎么配合适 <Connector port="8080" maxThreads="150&quo ...
- MEF学习总结(1)---总体架构
用了很久的MEF框架来做依赖注入,最近想把它的原理和机构总结一下,主要包括如下几个方面: 1. 总体架构 2. .Net Composition Primitive 3. Attribute Mode ...
- 部署webapp 至 tomcat 上出现 “There are no resources that can be added or removed from the server”
对要部署的项目右键---Properties---Myeclipse---选中Dynamic Web Module 和 Java
- Extjs tree1
1.代码如下: 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://w ...
- Python笔试面试题_牛客(待完善)
中文,免费,零起点,完整示例,基于最新的Python 3版本.https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42 ...
- Windows运行命令集锦
开始菜单中的“运行”(Win+R)是通向程序的快捷途径,输入特定的命令后,即可快速的打开Windows的大部分程序,熟练的运用它,将给我们的操作带来诸多便捷. winver 检查Windows版本 ...
- JVM内存管理之GC简介
为何要了解GC策略与原理? 原因在上一章其实已经有所触及,就是因为在平时的工作和研究当中,不可避免的会遇到内存溢出与内存泄露的问题.如果对GC策略与原理不了解的情况下碰到了前面所说的问题 ...
- 016:Explain
一. Explain EXPLAIN 官方文档 1.explain说明 explain是解释SQL语句的执行计划,即显示该SQL语句怎么执行的 使用explain的时候,也可以使用desc 5.6 版 ...