與下位機或設備的通訊解析優化的一點功能:T4+動態編譯

    去年接觸的一個項目中,須要經過TCP與設備進行對接的,傳的是Modbus協議的數據,而後後臺須要能夠動態配置協議解析的方式,即寄存器的解析方式,,配置信息有:Key,數據Index,源數據類型,數據庫列類型,數據排列方式 

    一開始使用的方式是,從數據庫讀取出協議的配置,而後在接收到數據的時候,循環每一個配置項根據配置-----解析數據------轉換類型----存臨時列表,,後來改進了一下,配置項存緩存,,數據庫修改的時候,同時更新緩存。。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     }
View Code

   這裏爲何要區分源數據和數據庫數據類型呢?主要是由於設備通常是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 }
View Code

   在直接在循環裏輸出解析後的語句,而且生成的類名記得後面加多一個隨機數。。。。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     }
View Code

   這樣,在運行時,便可直接生成可用於解析的類了,並且也不須要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

相關文章
相關標籤/搜索