对delegate进行扩展 打造通用的"计时完成"方法
连接数据库是程序员经常要做的事情, 通常情况下, 需要连接SQL Server中哪个数据库是已知的, 用户在输入SQL Server的服务器IP、用户名、密码和数据库名字后, 如果所有信息输入正确, 就可以建立连接了.
让用户尽量少打字
每次让用户输入这么多信息的确很糟糕, 可以改进一下设计: 服务器IP和用户名可以存放在配置文件里面, 初始化的时候默认加载到相应的文本框中; 从安全角度考虑, 密码必须经过用户手动输入; 而数据库名字则没必要让用户输入, 有了服务器IP、用户名、密码后可以尝试连接SQL Server, 连接SQL Server成功后, 把数据库中所有的数据库名加载到ComboBox让用户选择连接哪个数据库.

密码不正确

密码正确
如何实现
在后台代码中定义一个计时器, 设置它的Interval为1000毫秒, 用户输入密码时让定时器重新计时, 也就是说用户输入密码后, 如果在1秒钟内用户没有继续输入密码, 则会触发计时器的Elapsed事件, 这时程序尝试能不能连上SQL Server.
//用户停止输入密码1秒后自动尝试连接
private System.Timers.Timer m_timer=new System.Timers.Timer(1000);
private void txtPassword_PasswordChanged(object sender, RoutedEventArgs e)
{
m_timer.Stop();
m_timer.Start();
//清空数据库的选择列表
cbbDatabase.Items.Clear();
}
在计时器的Elapsed事件函数中, 程序尝试连接SQL Server, 但是如下写法会出现问题.
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
Dispatcher.BeginInvoke((Action)delegate()
{
var conStr = String.Format("Data Source={0};Integrated Security=False;User ID={1};Password={2};", txtServer.Text, txtUser.Text, txtPassword.Password);
using (var con = new SqlConnection(conStr))
{
try
{
con.Open();
}
catch
{
//TODO: 提示连接数据库失败
return;
}
//TODO:提示连接数据库成功
var sql = "select name from sys.databases";
var cmd = new SqlCommand(sql, con);
IAsyncResult asyncResult = cmd.BeginExecuteReader();
SqlDataReader reader = cmd.EndExecuteReader(asyncResult);
if (!reader.HasRows)
{
//TODO: 提示SQL Server中不存在数据库
return;
}
//TODO: 在这里释放Timer
while (reader.Read())
{
cbbDatabase.Items.Add(reader[0].ToString());
}
}
});
}
如果数据库的IP是错误的, con.Open()则要花费约30秒左右才会抛出异常, 这段时间UI线程会一直卡死, 经过测试, 30秒后才抛异常与数据库连接字符串的Connect Timeout属性没有关系.
可以写一个SqlConnection的扩展方法, 在这个方法里面用一个码表对执行con.Open()的时间计时, 用一个bool型变量标识有没有成功的con.Open(), 如果在规定的时间内没有成功连上SQL Server则抛出一个异常.
扩展SqlConnection
public static class SqlExtension
{
public static void TryOpen(this SqlConnection connection, int millisecondTimeout)
{
Stopwatch sw = new Stopwatch();
bool succeed = false;
Thread t = new Thread(() =>
{
try
{
sw.Start();
connection.Open();
//打开连接后设置succeed为true
succeed = true;
}
catch
{
}
});
t.IsBackground = true;
t.Start();
t.Join(millisecondTimeout);
t.Abort();
//如果没有成功, 则抛出一个异常
if (!succeed)
throw new Exception();
}
}
这样在原来的代码中调用con.TryOpen()就可以解决问题.
扩展delegate
其实扩展方法的第一个参数并不是一定要是一个class类, 其实delegate也是可以的, delegate其实在编译之后就是一个类, 如下改写刚刚的SqlConnection扩展方法, 就可以写一个通用的尝试操作的扩展方法.
public static class ActionExtension
{
public static void Invoke(this Action action, int millisecondTimeout)
{
Stopwatch sw = new Stopwatch();
bool succeed = false;
Thread t = new Thread(() =>
{
try
{
sw.Start();
action();
succeed = true;
}
catch
{
}
});
t.IsBackground = true;
t.Start();
t.Join(millisecondTimeout);
if (!succeed)
throw new Exception();
}
}
这样写, 代码则更为通用一些, 以后一些耗时的操作(当然要满足Action的签名)都可以调用这个方法, 在本例中, 可以如下调用:
((Action)con.Open).Invoke(2000);
作者:Create Chen
出处:http://technology.cnblogs.com
说明:文章为作者平时里的思考和练习,可能有不当之处,请博客园的园友们多提宝贵意见。
本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。
连接数据库是程序员经常要做的事情, 通常情况下, 需要连接SQL Server中的哪个数据库是已知的, 用户在输入SQL Server的服务器IP、用户名、密码和数据库名字后, 如果所有信息输入正确, 就可以建立连接了. 让用户尽量少打字 每次让用户输入这么多信息的确很糟糕, 可以改进一下设计: 服务器IP和用户名可以存放在配置文件里面, 初始化的时候默认加载到...
浙公网安备 33010602011771号