现在我们要集中精力实现一个实战实例来描述到目前为止我们已经看过的内容。这里要实现的DataImport 例子是那种等待文件到达指定目录然后将其导入到一个SQL Server 数据库中的典型程序。下面我们列出了这个例子中将要使用的类:
FileSystemWatcher: 这个类允许开发人员监控指定目录并能够在发生改变时(比如创建一个新文件或者删除一个文件)触发事件。这个类位于System.IO 命名空间中。
TextWriterTraceListener: 实现我们自己的跟踪功能。
Thread: 已经看过很多遍了,允许我们启动一个新线程来把数据导入到数据库中。
很多SqlClient 命名空间里的类,用于管理SQL Server 数据库连接和更新。
第一版的DataImport 故意加了点逻辑错误,这样你就可以使用跟踪功能的日志文件并了解其重要性。
代码如下:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o [email protected] * */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.IO; using System.Threading; namespace DataImport { class DataImport { public static BooleanSwitch bs; static FileSystemWatcher fsw; [STAThread] static void Main(string[] args) { //Remove the default listener Trace.Listeners.RemoveAt(0); //Create and add the new listener bs = new BooleanSwitch("DISwitch", "DataImport switch"); Trace.Listeners.Add(new TextWriterTraceListener( new FileStream(@"D:\DataImport.log", FileMode.OpenOrCreate))); //Create a FileSystemWatcher object used to monitor the specified directory fsw = new FileSystemWatcher(); fsw.Path = @"D:\Temp"; fsw.Filter = "*.xml"; fsw.IncludeSubdirectories = false; fsw.Created += new FileSystemEventHandler(OnFileCreated); fsw.EnableRaisingEvents = true; try { WaitForChangedResult res; while (true) { res = fsw.WaitForChanged(WatcherChangeTypes.Created); Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- Found: " + res.Name + " file"); } } catch (Exception ex) { Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- An exception occured while waiting for file :"); Trace.Indent(); Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- " + ex.ToString()); Trace.Unindent(); } finally { fsw.EnableRaisingEvents = false; Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- Directory monitoring stopped"); Trace.Close(); } } static void OnFileCreated(object sender, FileSystemEventArgs e) { try { //Create a new object from the ImportData class to process the incoming file ImportData id = new ImportData(); id.mStrFileName = e.FullPath; Thread t = new Thread(new ThreadStart(id.Import)); t.Name = "DataImportThread"; t.Start(); } catch (Exception ex) { Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- An exception occured while queuing file:"); Trace.Indent(); Trace.WriteLineIf(bs.Enabled, DateTime.Now + "- " + ex.ToString()); Trace.Unindent(); } finally { Trace.Flush(); } } } }
按照以下步骤测试程序:
创建一个C:\Temp 目录来存储XML 文件
运行DataImport 程序
把authors.xml 文件放到C:\Temp 目录中
最终结果可以从DataImport.log 文件中找到,结果如下:
2012/4/28 13:41:20- Found: Authors.xml file
2012/4/28 13:41:20- Filling the DataSet
2012/4/28 13:41:20- Reading XML file
2012/4/28 13:41:20- DatSet filled with data.
2012/4/28 13:41:20- Updating database...
2012/4/28 13:41:20- Database updated successfully.
2012/4/28 13:41:23- Total TIME: 00:00:02.0370000 seconds
authors.xml 文件不大,整个运行时间非常短。
看起来所有的都按照期望的那样运行了,但是还有些情况没有覆盖到。到目前为止,我们使用小文件测试程序,当程序接收到文件创建事件后会打开文件,然后把文件拷贝到指定目录中并关闭文件。如果我们接收一个大文件会出现什么情况?结果会是当线程尝试访问XML文件并填充DataSet 对象时,它会接收到一个尝试打开正在使用的文件异常。现在我们拷贝huge_authors.xml 文件来再测试一遍程序。由于我们使用了跟踪功能,你可以在日志文件中找到以下错误:
2012/4/28 13:53:14- Found: Authors.xml file
2012/4/28 13:53:14- Filling the DataSet
2012/4/28 13:53:14- Reading XML file
2012/4/28 13:53:22- A general exception occured during file processing:
2012/4/28 13:53:22- System.IO.IOException:
The process cannot access the file 'D:\Temp\Authors.xml' because it is being used by another process.
对这种类型的错误调试器通常捕获不到,因为用来运行程序的时间和调试到代码的时间足够用来拷贝一个文件。在你的环境中也不一定发生,发生这个问题关键在于你的硬盘访问速度以及内存大小。
在我们调用ReadXml() 方法之前,你应该尝试显示打开文件。如果发生了一个错误,那么你可以阻塞线程几秒,然后在文件可以处理时再尝试一次。让我们看看DataImport2 是如何实现的,添加了一个GetFileAccess() 方法:
private bool GetFileAccess() { Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Trying to get exclusive access to the " + mStrFileName + " file."); FileStream fs = null; try { fs = new FileStream(mStrFileName, FileMode.Append, FileAccess.Write, FileShare.None); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Access to the " + mStrFileName + " file allowed."); return true; } catch { Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Access denied to the " + mStrFileName + " file."); return false; } finally { if (fs != null) { fs.Close(); } } }
GetFileAccess()方法会返回一个布尔值来指示是否可以访问指定文件。这个方法将共享访问属性设置为空并尝试打开文件:
public void Import() { string connectionStr = @"Data Source=.\SQLEXPRESS;Initial Catalog=pubs;Integrated Security=True"; SqlConnection dbConn = new SqlConnection(connectionStr); SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM authors", dbConn); DataSet ds = new DataSet(); SqlCommandBuilder sa = new SqlCommandBuilder(da); try { while (!GetFileAccess()) { Thread.Sleep(5000); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Trying to access to the " + mStrFileName + " file again."); } Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Filling the DataSet"); da.Fill(ds); DataSet dsMerge = new DataSet(); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Reading XML file"); Thread.Sleep(8000); dsMerge.ReadXml(mStrFileName, XmlReadMode.InferSchema); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- DatSet filled with data."); DateTime time = DateTime.Now; Trace.WriteLineIf(DataImport.bs.Enabled, time + "- Updating database..."); da.TableMappings.Add("Table", "authors"); da.InsertCommand = sa.GetInsertCommand(); da.Update(dsMerge); DateTime time2 = DateTime.Now; Trace.WriteLineIf(DataImport.bs.Enabled, time + "- Database updated successfully."); Trace.Indent(); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- Total TIME: " + time2.Subtract(time) + " seconds"); Trace.Unindent(); } catch (SqlException ex) { Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- A SQL exception occured during file processing: "); Trace.Indent(); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- " + ex.ToString()); Trace.Unindent(); } catch (Exception e) { Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- A general exception occured during file processing: "); Trace.Indent(); Trace.WriteLineIf(DataImport.bs.Enabled, DateTime.Now + "- " + e.ToString()); Trace.Unindent(); } finally { Trace.Flush(); } }
ImportData.Import() 方法显式访问文件。如果文件仍然被拷贝任务使用,线程会阻塞5秒。直到源文件可以被打开时才可以调用GetFileAccess() 方法。
到目前为止我们已经通过实用例子了解了跟踪功能对在程序运行阶段了解其行为是很重要的。
在这一章我们已经了解如何使用Visual Studio.NET 调试器来观察一个程序在执行过程中的行为。同时我们也学习使用调试器提供的允许我们检查并改变一个变量值的强大工具,等等。
在本章的第二部分,我们使用.NET 提供的三个类来讲解跟踪功能:Trace, Debug 和 Switch. 我们通过在应用程序配置文件中修改值来动态地激活/反激活跟踪功能。
最后,我们通过一个实例学习了帮助开发人员找到并修改问题和逻辑错误的跟踪技术。
下一篇介绍 第七章 网络和线程…