获取与邮政编码相对应的地名






3.07/5 (11投票s)
如何将 GeoNames.org 的数据库转储导入 SQL Server

引言
有各种 Web 服务可以在您输入邮政编码时提供相应的地点名称。GeoNames 就是这样一个服务。
作为一项额外服务,GeoNames 在知识共享 3.0 许可下提供数据库。首先,您需要从他们最新的数据库转储中下载特定国家的 zip 文件并解压。
Using the Code
如果您只对将数据导入数据库感兴趣,那么可以运行演示。输入一个有效的连接字符串,将文件对话框指向您解压的下载文件,然后等待进度条填满。
源代码相对简单,因为这是一个直接的导入任务。导入逻辑 nicely 封装在“FormMain.ImportProcedure.cs”文件中。该文件包含基于 foreach
循环的 BackgroundWorker 逻辑。
private void backgroundWorker1_DoWork
(object sender, System.ComponentModel.DoWorkEventArgs e)
{
[...]
string[] lines = File.ReadAllLines(args.SourceFileName, Encoding.UTF8);
foreach (string line in lines)
{
//The data format is tab-delimited text in utf8 encoding..
string[] items = line.Split('\t');
IDbCommand sqlInsertItem = db.NewCommand(sqlInsertIntoImportTable);
sqlInsertItem.Parameters.Add(db.NewParam("countrycode", items[0]));
[...]
sqlInsertItem.Parameters.Add(db.NewParam("longitude", items[9]));
sqlInsertItem.Parameters.Add(db.NewParam("accuracy", items[10]));
sqlInsertItem.ExecuteNonQuery();
支持不同的数据库驱动程序
此版本支持多种数据库格式,并已在 SQL Server、Microsoft Access、SQL CE、SQLite、Oracle XE 和 Microsoft Excel 中进行了测试。
导入逻辑不需要知道所用数据库驱动程序的具体细节,而是使用了一个接口。接口被替换为用户选择的等效驱动程序,在运行时创建对象来替换 IConnection
。创建适当对象的任务由内部 DataInterface
类通过调用 static New
方法完成。
然后,此方法会根据用户在下拉列表中选择的驱动程序进行 switch
。如果用户想要 SqlClient
,我们将返回一个 SqlConnection
。如果用户想要 OracleClient
连接,我们将返回一个促进 Oracle 的连接。
switch (dbType)
{
case DataProvider.ODBC:
{
result._con = new System.Data.Odbc.OdbcConnection(connectionString);
break;
}
case DataProvider.OleDB:
{
result._con = new System.Data.OleDb.OleDbConnection(connectionString);
break;
}
case DataProvider.OracleClient:
{
result._con =
new System.Data.OracleClient.OracleConnection(connectionString);
break;
}
case DataProvider.SqlClient:
{
result._con = new System.Data.SqlClient.SqlConnection(connectionString);
[...]
可以通过打开连接并发布包含 SQL 语句的 IDbCommands
来访问这些数据库中的每一个。大多数数据库可以处理 ANSI SQL 命令。有关 ANSI 标准的更多信息,请参阅 Asher Barak 的文章“Server Indifferent SQL”。
连接字符串
一些在测试此代码时使用的示例连接及其测试结果
SQL Server Express 2005 (提供程序: SqlClient)
Server=.\SQLEXPRESS;Database=master;Trusted_Connection=True;
16377 条记录,耗时 1 分钟 4 秒 - 平均每条记录 0.0039 秒。
Microsoft Access (提供程序: OleDB)
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\database1.mdb;User Id=admin;Password=;
16377 条记录,耗时 54 秒,平均每条记录 0.0033 秒。
SQL CE (提供程序: SqlCe)
Data Source=C:\MyDatabase1.sdf;
16377 条记录,耗时 38 秒,平均每条记录 0.023 秒。
SQLite (提供程序: SQLite)
Data Source=C:\Database.sqlite;Version=3;
16377 条记录,耗时 22 分钟 31 秒,平均每条记录 0.0825 秒。
Oracle XE 10g (提供程序: OracleClient)
Data Source=XE;User Id=Anonymous;Password=Password;
16377 条记录,耗时 48 秒,平均每条记录 0.0030 秒。
Microsoft Excel 2007 (提供程序: OleDb)
Provider=Microsoft.ACE.OLEDB.12.0;Data Source=c:\Map1.xlsx;
Extended Properties="Excel 12.0 Xml;HDR=YES";
16377 条记录,耗时 1 分钟 3 秒,平均每条记录 0.0039 秒。
还需要多久
有一个 ProgressBar
提供关于我们进度的视觉反馈。此进度条会根据我们机器的速度快或慢地填充。如果进度条填充得太慢,我们的反馈值就会下降——因为用户没有得到真实的反馈——他只是在等待进度条中出现另一个条,而不知道这个下一个条何时出现。
一种选择是使进度条更长,从而提供更精细的进度指示。另一种选择是添加一个“估计等待时间”,就像您在 Windows 资源管理器复制文件时看到的那样。当操作由耗时相等的多个工作块组成时,这将效果最好。我们需要跟踪操作开始的时间。接下来我们需要知道的是我们正在为当前操作工作了多久,所以我们从 startMoment
中减去当前日期/时间。
DateTime now = DateTime.Now;
TimeSpan span = now.Subtract(startMoment);
现在我们知道处理了多久,我们可以计算平均每个项目花了多长时间。
// how long does it take to do a single item?
double timePerItem = (span.TotalSeconds / progressBar1.Value);
一旦您知道平均项目处理需要多长时间(这是 progressBar
中的一个单独步骤),我们就可以计算出执行剩余操作(可能)需要多长时间。当您将鼠标指针悬停在 progressBar
上时,您可以看到结果。

注释
- SQLite 比预期慢了不少。一定有一种更有效的方式来与 SQLite 数据库进行交互。
- Oracle 不喜欢参数声明中的
@
符号。运行时一直困扰我“ORA-01036: 非法的变量名/数字”的错误,直到 SQL 语句中的@
字符被替换为:
字符。
历史
- 2009 年 1 月 21 日 - 初始版本
- 2009 年 2 月 8 日 - 解耦数据库依赖,并将导入逻辑移至 BackgroundWorker