一開始使用的方式是,從數據庫讀取出協議的配置,而後在接收到數據的時候,循環每一個配置項根據配置-----解析數據------轉換類型----存臨時列表,,後來改進了一下,配置項存緩存,,數據庫修改的時候,同時更新緩存。。mongodb
但仍是慢了一點,由於需一個配置大概是20-30個數據項,每一條數據都要for循環20-30次 ,再加上還有N個根據配置的數據類型去作轉換的if判斷,這麼一套下來,也很耗時間,但待解析的數據量大的狀況下,,相對也很耗資源。。。數據庫
最後的以爲方案是:利用T4生成C#的class源碼+運行時編譯成類,數據直接扔class裏直接解析出結果,不須要循環,也不須要if判斷,由於在t4生成源碼的時候,已經根據配置處理完了,所以節省了不少的時間。數組
不過因爲T4模板的IDE支持的很很差,不過好在運行時T4模板在IDE內生成出來的類是partial的,所以,能夠把大部分的代碼,放在外部的C#文件裏。先來看數據項的配置信息:緩存
1 public class DataItem 2 { 3 /// <summary> 4 /// 數據項ID 5 /// </summary> 6 public ObjectId DataItemID { set; get; } 7 8 /// <summary> 9 /// 偏移量 10 /// </summary> 11 public int Pos { set; get; } 12 13 /// <summary> 14 /// 大小 15 /// </summary> 16 public int Size { set; get; } 17 18 public int BitIndex { set; get; } 19 20 /// <summary> 21 /// 數據項數據庫儲存類型 22 /// </summary> 23 public DbDataTypeEnum DbType { set; get; } 24 25 /// <summary> 26 /// 數據項協議源字節數組中的數據類型 27 /// </summary> 28 public DataTypeEnum SourceType { set; get; } 29 30 /// <summary> 31 /// 計算因子 32 /// </summary> 33 public decimal Factor { set; get; } 34 35 public string Key { set; get; } 36 } 37 38 /// <summary> 39 /// 對應的數據庫字段類型 40 /// </summary> 41 public enum DbDataTypeEnum 42 { 43 Int32 = 0, 44 45 Int64 = 1, 46 47 Double = 2, 48 49 DateTime = 3, 50 51 Decimal = 4, 52 53 Boolean = 5 54 } 55 56 public enum DataTypeEnum 57 { 58 Int = 0, 59 60 Short = 1, 61 62 Datetime = 3, 63 64 Long = 5, 65 66 Decimal = 6, 67 68 UInt = 7, 69 70 Byte = 8, 71 72 Boolean = 9, 73 74 Bit = 10, 75 76 UShort = 11, 77 78 UByte = 12 79 }
這裏爲何要區分源數據和數據庫數據類型呢?主要是由於設備通常是int,short,double,float等類型,可是,對應到數據庫,有時候好比說使用mongodb,之類的數據庫,不必定有徹底匹配的,所以須要區分兩種數據項,ide
再來就是T4的模板 ProtocolExecuteTemplate.tt:this
1 <#@ template language="C#" #> 2 <#@ assembly name="System.Core" #> 3 <#@ assembly name="Kugar.Core.NetCore" #> 4 <#@ assembly name="Kugar.Device.Service.BLL" #> 5 <#@ assembly name="Kugar.Device.Service.Data" #> 6 <#@ assembly name="MongoDB.Bson" #> 7 8 <#@ import namespace="System.Linq" #> 9 <#@ import namespace="System.Text" #> 10 <#@ import namespace="System.Collections.Generic" #> 11 <#@ import namespace="Kugar.Core.BaseStruct" #> 12 <#@ import namespace="MongoDB.Bson" #> 13 14 using System; 15 using System.Text; 16 using Kugar.Core.BaseStruct; 17 using Kugar.Core.ExtMethod; 18 using Kugar.Core.Log; 19 using Kugar.Device.Service.Data.DTO; 20 using Kugar.Device.Service.Data.Enums; 21 using MongoDB.Bson; 22 23 namespace Kugar.Device.Service.BLL 24 { 25 <# 26 var className="ProtocolExecutor_" + Protocol.Version.Replace('.','_') + "_" + this.GetNextClasID(); 27 #> 28 29 30 public class <#=className #>:IProtocolExecutor 31 { 32 private string _version=""; 33 private ObjectId _protocolID; 34 private readonly DateTime _baseDt=TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); 35 36 public <#=className #> (ObjectId protocolID, string version) 37 { 38 _version=version; 39 _protocolID=protocolID; 40 } 41 42 public ObjectId ProtocolID {get{return _protocolID;}} 43 44 public BsonDocument Execute(byte[] data, int startIndex) 45 { 46 BsonDocument bson=new BsonDocument(); 47 <# 48 foreach(var item in Protocol.Items){ #> 49 bson["<#=item.Key #>"]= <#= DecodeConfig(item,0) #>; 50 <# 51 } 52 #> 53 54 return bson; 55 56 } 57 } 58 }
在直接在循環裏輸出解析後的語句,而且生成的類名記得後面加多一個隨機數。。。。spa
而後再加一個ProtocolExecuteTemplate.Part.cs的部分類,補全T4模板的功能,由於在T4裏IDE支持的很差,,寫代碼確實難受,,沒直接寫C#舒服:code
1 public partial class ProtocolExecuteTemplate 2 { 3 private static int _classID = 0; 4 5 public ProtocolExecuteTemplate(DTO_ProtocolDataItem protocol) 6 { 7 Protocol = protocol; 8 9 } 10 11 12 13 public DTO_ProtocolDataItem Protocol { set; get; } 14 15 public string DecodeConfig(DTO_ProtocolDataItem.DataItem item,int startIndex) 16 { 17 var str = ""; 18 19 switch (item.SourceType) 20 { 21 case DataTypeEnum.Int: 22 str = $"BitConverter.ToInt32(data,startIndex + {startIndex + item.Pos})"; 23 break; 24 case DataTypeEnum.UInt: 25 str = $"BitConverter.ToUInt32(data,startIndex + {startIndex + item.Pos})"; 26 break; 27 case DataTypeEnum.Short: 28 str = $"BitConverter.ToInt16(data,startIndex + {startIndex + item.Pos})"; 29 break; 30 case DataTypeEnum.Long: 31 str= $"BitConverter.ToInt64(data,startIndex + {startIndex + item.Pos})"; 32 break; 33 case DataTypeEnum.Byte: 34 case DataTypeEnum.UByte: 35 case DataTypeEnum.Bit: 36 str = $"data[startIndex + {startIndex + item.Pos}]"; 37 break; 38 case DataTypeEnum.UShort: 39 str = $"BitConverter.ToUInt16(data,startIndex+{startIndex + item.Pos})"; 40 break; 41 default: 42 throw new ArgumentOutOfRangeException(); 43 } 44 45 if (item.SourceType==DataTypeEnum.Bit) 46 { 47 return byteToBit(str, item.BitIndex); 48 } 49 else 50 { 51 return valueTODBType(str, item.Factor, item.DbType); 52 } 53 } 54 55 private string valueTODBType(string sourceValue, decimal factor, DbDataTypeEnum dbType) 56 { 57 switch (dbType) 58 { 59 case DbDataTypeEnum.Int32: 60 return $" new BsonInt32({(factor > 0 ? $"(int)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})"; 61 case DbDataTypeEnum.Int64: 62 return $" new BsonInt64({(factor > 0 ? $"(long)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})"; 63 case DbDataTypeEnum.Double: 64 return $"new BsonDouble({(factor > 0 ? $"(double)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : $"(double){sourceValue}")})"; 65 case DbDataTypeEnum.DateTime: 66 return $"new BsonDateTime(_baseDt.AddSeconds({sourceValue}))"; 67 case DbDataTypeEnum.Decimal: 68 return $"new Decimal128({(factor > 0 ? $"(decimal)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})"; 69 default: 70 throw new ArgumentOutOfRangeException(nameof(dbType), dbType, null); 71 } 72 } 73 74 private string byteToBit(string data, int index) 75 { 76 switch (index) 77 { 78 case 0: 79 { 80 81 return $"(({data} & 1) ==1)";//(data & 1) == 1; 82 } 83 case 1: 84 { 85 return $"(({data} & 2) ==1)";// (data & 2) == 2; 86 } 87 case 2: 88 { 89 return $"(({data} & 4) ==1)";//(data & 4) == 4; 90 } 91 case 3: 92 { 93 return $"(({data} & 8) ==1)";//(data & 8) == 8; 94 } 95 case 4: 96 { 97 return $"(({data} & 16) ==1)";//(data & 16) == 16; 98 } 99 case 5: 100 { 101 return $"(({data} & 32) ==1)";//(data & 32) == 32; 102 } 103 case 6: 104 { 105 return $"(({data} & 64) ==1)";//(data & 64) == 64; 106 } 107 case 7: 108 { 109 return $"(({data} & 128) ==1)";//(data & 128) == 128; 110 } 111 default: 112 throw new ArgumentOutOfRangeException(nameof(index)); 113 } 114 115 return $"(({data} & {index + 1}) ==1)"; 116 } 117 118 /// <summary> 119 /// 用於判斷傳入的fator是否須要使用deciaml進行運算,若是有小數點的,則是否decimal縮寫m,,若是沒有小數點,則使用普通的int類型 120 /// </summary> 121 /// <param name="value"></param> 122 /// <returns></returns> 123 private string getDecimalShortChar(decimal value) 124 { 125 return (value % 1) == 0 ? "" : "m"; 126 } 127 128 public int GetNextClasID() 129 { 130 return Interlocked.Increment(ref _classID); 131 } 132 }
這樣,在運行時,便可直接生成可用於解析的類了,並且也不須要for循環判斷,生成出來的類如:blog
1 public class ProtocolExecutor_1_1_000 2 { 3 public BsonDocument Execute(byte[] data, int startIndex) 4 { 5 BsonDocument bson = new BsonDocument(); 6 7 bson["項目名1"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量)); 8 bson["項目名2"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量)); 9 。。。。。。。 //其餘數據項 10 11 return bson; 12 } 13 }
到這一步,就能夠根絕配置項生成出對應的C#代碼了,剩下的就是動態編譯的事情了、將該代碼編譯出運行時Type,而後傳入數據----解析出數據ci