1.利用SqlBulkCopy.WriteToServer(IDataReader reader)方法批量导入日志文件中的记录到SQL SERVER数据库。
2.自定义的TxtDataReader类实现IDataReader接口用于传递给SqlBulkCopy.WriteToServer使用。
3.在TxtDataReader的实现中利用正则表达式分组捕获需要的信息。
第一步:实现自定义的TxtDataReader类
1.代码中的未列出实现的IDateReader接口成员对当前实现的功能没有影响。
2.TxtDataReader构造函数的pattren参数格式必须是:命名分组_类型,类型只简单的匹配了int|date|string三种。
1 partial class TxtDataReader : System.Data.IDataReader
2 {
3 string pattern;
4 int fieldCount;
5 TextReader reader;
6 NameValueCollection FieldInfo;
7 NameValueCollection items;
8 bool isClosed;
9 /// <summary>
10 /// 私有构造函数,内部调用创建TxtDataReader的实例
11 /// </summary>
12 /// <param name="file">要处理的文本</param>
13 /// <param name="pattern">匹配每行文本的正则表达式</param>
14 private TxtDataReader(string file,string pattern)
15 {
16 this.pattern=pattern;
17 MatchCollection matches = Regex.Matches(pattern, "\\(\\?<([^<>]+)_(int|date|string)>");
18 this.fieldCount = matches.Count;
19 this.FieldInfo = new NameValueCollection();
20 for (int i = 0; i < matches.Count; i++)
21 {
22 this.FieldInfo.Add(matches[i].Groups[1].Value, matches[i].Groups[2].Value);
23 }
24 this.reader = new StreamReader(file);
25 this.isClosed = false;
26 }
27 /// <summary>
28 /// 静态方法返回TxtDataReader的实例
29 /// </summary>
30 public static TxtDataReader ExecuteReader(string file,string pattern)
31 {
32 return new TxtDataReader(file, pattern);
33 }
34 /// <summary>
35 /// 列数
36 /// </summary>
37 public int FieldCount
38 {
39 get { return this.fieldCount; }
40 }
41 /// <summary>
42 /// 读取并处理文件的下一行
43 /// </summary>
44 public bool Read()
45 {
46 if (this.isClosed)
47 {
48 return false;
49 }
50 string line = this.reader.ReadLine();
51 if (line == null)
52 {
53 this.isClosed = true;
54 return false;
55 }
56 GroupCollection groups = Regex.Match(line, this.pattern).Groups;
57 items = new NameValueCollection();
58 for (int i = 0; i < this.FieldInfo.Count; i++)
59 {
60 string value = groups[FieldInfo.GetKey(i)+"_"+this.FieldInfo[i]].Value;
61 if (this.FieldInfo[i] == "date")
62 {
63 string format = "[dd/MMM/yyyy:HH:mm:ss zzzz]";
64 value = DateTime.ParseExact(value, format, new CultureInfo("en-US", true)).ToString();
65 }
66 if (this.FieldInfo[i] == "int")
67 {
68 try
69 {
70 value = Convert.ToInt32(value).ToString();
71 }
72 catch (Exception ex)
73 {
74 value = "0";
75 }
76 }
77 items.Add(FieldInfo.GetKey(i), value);
78 }
79
80 return true;
81 }
82 /// <summary>
83 /// 根据列索引返回列值
84 /// </summary>
85 public object GetValue(int i)
86 {
87 return items[i];
88 }
89 /// <summary>
90 /// 返回读取器的状态
91 /// </summary>
92 public bool IsClosed
93 {
94 get { return this.isClosed; }
95 }
96 /// <summary>
97 /// 关闭读取器
98 /// </summary>
99 public void Close()
100 {
101 this.reader.Close(); ;
102 }
103 #region IDisposable 成员
104
105 public void Dispose()
106 {
107 this.Close();
108 }
109
110 #endregion
111 }
第二步:构造用于 TxtDataReader解析日志每行记录的正则表达式
正则表达式 | 类型 | 备注 | 描述 |
^ | 匹配每一行的开头。 | ||
(?<ip_string>[0-9.]+)\s | string | not null | 匹配IP地址。 |
(?<identity_string>[\w.-]+)\s | string | 可能为"-" | 匹配identity,由数字字母下划线或点分隔符组成。 |
(?<userid_string>[\w.-]+)\s | string | 可能为"-" | 匹配userid,由数字字母下划线或点分隔符组成。 |
(?<date_date>\[[^\[\]]+\])\s" | datetime | not null | 匹配时间。 |
(?<request_string>(?:[^"]|\")*)"\s | string | 可能为"" | 匹配请求信息。 |
(?<status_int>\d{3})\s | int | 3个整数 | 匹配状态码。 |
(?<bytes_int>\d+|-)\s" | int | 可能为"-" | 匹配响应字节数或-。 |
(?<ref_string>(?:[^"]|\")*)"\s" | string | 可能为"-" | 匹配"Referer"请求头,双引号中可能出现转义的双引号\"。 |
(?<useragent_string>(?:[^"]|\")*)" | string | 可能为"-" | 匹配"User-Agent"请求头,双引号中可能出 现转义的双引号\"。 |
$ | 匹配行尾。 |
完整的正则表达式如下:
^(?<ip_string>[0-9.]+)\s(?<identity_string>[\w.-]+)\s(?<userid_string>[\w.-]+)\s(?<date_date>\[[^\[\]]+\])\s"(?<request_string>(?:[^"]|\")*)"\s(?<status_int>\d{3})\s(?<bytes_int>\d+|-)\s"(?<ref_string>(?:[^"]|\")*)"\s"(?<useragent_string>(?:[^"]|\")*)"$
第三步:创建用于导入的表格
CREATE TABLE [dbo].[test](
[ip] [nvarchar](64),
[identity] [nvarchar](64),
[userid] [nvarchar](64),
[date] [datetime] NULL,
[request] [nvarchar](2000),
[status] [int] NULL,
[bytes] [int] NULL,
[ref] [nvarchar](2000),
[useragent] [nvarchar](2000) NULL
)
第四步:使用SqlBulkCopy导入数据
1 static void Main(string[] args)
2 {
3 string pattern = "^(?<ip_string>[0-9.]+)\\s(?<identity_string>[\\w.-]+)\\s(?<userid_string>[\\w.-]+)\\s(?<date_date>\\[[^\\[\\]]+\\])\\s\"(?<request_string>(?:[^\"]|\\\")+)\"\\s(?<status_int>\\d{3})\\s(?<bytes_int>\\d+|-)\\s\"(?<ref_string>(?:[^\"]|\\\")+)\"\\s\"(?<useragent_string>(?:[^\"]|\\\")+)\"$";
4 string conn = "Data Source=.;Initial Catalog=seodb;Integrated Security=True";
5 using (SqlBulkCopy sbc = new SqlBulkCopy(conn))
6 {
7 TxtDataReader reader = TxtDataReader.ExecuteReader(file, pattern);
8 sbc.DestinationTableName = "dbo.test";
9 try
10 {
11 sbc.WriteToServer(reader);
12 }
13 catch (Exception ex)
14 {
15
16 }
17 finally
18 {
19 reader.Close();
20 }
21 }
22
23 }
备注:
1.如果其他场合需要以reader[int]或reader[string]形式获取读取器重的方法,需实现索引器。如需使用其他IDataReader接口定义的方法,同样。
参考代码:
1 public object this[string name]
2 {
3 get { return items[name]; }
4 }
5
6 public object this[int i]
7 {
8 get { return items[i]; }
9 }
//Console.WriteLine(reader["ip"]);
2.为让示例代码简单清晰,其他未提供具体实现的IDataReader代码放在了分部类中。
3.当前所述解决的问题是解析日志文件并导入数据库中,只提供了满足当前需求的精简代码,甚至不包含必要的异常处理,数据截断、溢出等处理,如在实际用途中使用,需自行扩展和添加代码。
4.简单的、仅具参考价值的测试:windows xp\.net 2.0 \sql server 2005\2G内存 217M大小正式站日志文件,处理十次,平均时间35秒。