[转载]通过SQL Server 2008数据库复制实现数据库同步备份

mikel阅读(908)

[转载]通过SQL Server 2008数据库复制实现数据库同步备份 – dudu – 博客园.

SQL Server 2008数据库复制是通过发布/订阅的机制进行多台服务器之间的数据同步,我们把它用于数据库的同步备份。这里的同步备份指的是备份服务器与主服务器进行 实时数据同步,正常情况下只使用主数据库服务器,备份服务器只在主服务器出现故障时投入使用。它是一种优于文件备份的数据库备份解决方案。

在选择数据库同步备份解决方案时,我们评估了两种方式:SQL Server 2008的数据库镜像和SQL Server 2008数据库复制。数据库镜像的优点是系统能自动发现主服务器故障,并且自动切换至镜像服务器。但缺点是配置复杂,镜像数据库中的数据不可见(在SQL Server Management Studio中,只能看到镜像数据库处于镜像状态,无法进行任何数据库操作,最简单的查询也不行。想眼见为实,看看镜像数据库中的数据是否正确都不行。只 有将镜像数据库切换主数据库才可见)。如果你要使用数据库镜像,强烈推荐killkill写的SQL Server 2005 镜像构建手册,我们就是按照这篇文章完成了数据库镜像部署测试。

最终,我们选择了SQL Server 2008数据库复制。

下面通过一个示例和大家一起学习一下如何部署SQL Server 2008数据库复制。

测试环境:Windows Server 2008 R2 + SQL Server 2008 R2(英文版),两台服务器,一台主数据库服务器CNBlogsDB1,一台备份数据库服务器CNBlogsDB2。

复制原理:我们采用的是基于快照的事务复制。主数据库服务器生成快照,备份库服务器读取并加载该快照,然后不停地从主数据库服务器复制事务日志。见下图:

grid.ai

图片来自SQL Server联机丛书

安装与配置步骤:

一、在两台服务器上安装好SQL Server 2008 R2,主要安装的组件:Database Engine(含SQL Server Replication),Management Tools。

二、主数据库服务器(发布服务器)的配置:

1. 在主数据库服务器CNBlogsDB1新建示例数据库CNBlogsDemo(注意Recovery mode要使用默认值Full,只有这个模式才能进行事务复制),然后建立一张测试表,比如:CNBlogsTest。

cnblogs_test

2. 设置存放快照的文件夹:

创建发布之前,先设置一下存放快照的文件夹,创建发布后会在该文件夹生成快照文件,订阅服务器需要在初始化时加载该快照文件。

选择Replication》Local Publications》属性,在出现的窗口中选择Publishers,如下图:

20100826-8

点击红框处的按钮,出现设置窗口:

20100826-9

在Default Snapshot Folder中设置快照文件存放路径。

3. 在主数据库服务器创建发布:

在Replication》Local Publications中选择New Publication,出现一个向导。先选择要发布的数据库CNBlogsDemo,然后选择发布类型Transational publication,如下图:

Transationalpublication

点击Next,出现错误:

20100826-1

原来所有要复制的表都需要有主键,刚才建CNBlogsTest表时,没有建主键。建一下主键,并重新启动向导就可以了。

接着选择要复制的对象:

20100826-2

点Next,Next,进入Snapshot Agent窗口,选择Create a snapshot immediately and keep the snapshot available to initialize subscriptions,见下图:

20100826-3

Next,进入Agent Security:

20100826-4

选择Security Settings,进行相应的帐户设置:

20100826-5

一个是设置运行Snapshot Agent的Windows帐户,我们这里选择与SQL Server Agent同样的帐户。

一个是设置连接发布服务器的SQL帐户,我们这里就用主数据库服务器的sa帐户。

继续:OK,Next,Next,为这个发布起个名字:

20100826-7

点击Finish,就开始正式创建发布,创建成功就会出现如下窗口:

20100826-10

这时查看快照文件夹,就会看到unc文件夹,快照文件就在这个文件夹中。

这里要考虑这样一个问题,如何让订阅服务器通过网络访问这个快照文件夹。

我们在这个问题上折腾了一些时间,本来想通过共享文件夹的方式,但又不想打开匿名共享,折腾了半天,没搞定订阅服务器访问共享文件夹用户验证的问题。于是采用了FTP的方式,所以,下面介绍一下如何让订阅服务器通过FTP访问快照文件。

4. 设置快照的FTP访问

首先在主数据库服务器上开通FTP服务,建立一个指向快照文件夹的FTP站点,设置好可以远程连接的FTP帐户。然后在这台发布服务器设置一下FTP客户端配置。配置方法如下:

在Replication》Local Publications中选择刚才创建的发布[CNBlogsDemo]:CNBlogsDemo_Publication,选择属性》FTP Snapshot,如下图:

20100826-11

选中Allow Subscribers to download snapshot files using FTP,并设置一下FTP客户端连接参数,订阅服务器就是通过这里的设置连接FTP服务器的(注:Path from the FTP root folder的设置要和上图一样,设置为:/ftp)。

点击OK,这时会在快照文件夹中创建一个ftp文件夹,并在该文件夹中生成快照文件。

这样,发布服务器就配置好了,下面配置订阅服务器。

三、备份数据库服务器(订阅服务器)的配置:

进入订阅服务器CNBlogsDB2,创建与发布服务器同名的数据库CNBlogsDemo,使用完全恢复模式。

在Replication》Local Subscriptions中选择New Subscriptions,进入向导。

Next,进入选择发布服务器的窗口,选择Find SQL Server Publisher,出现服务器连接窗口:

20100826-12

这里要注意的是Server Name中一定要填写发布服务器的计算机名,如果计算机名连接不上,要在hosts文件中加一个IP地址解析。

成功连接发布服务器之后,就可以看到刚才在主数据库服务器上创建的发布:

20100826-13

Next,进入“分发代理工作位置”的选择窗口:

20100826-14

我们这里选择pull subscriptions,把数据给拉过来,这样主数据库服务器的负担会轻些。

Next,选择订阅服务器上的数据库,之前我们已经建好同名的数据库,所以系统自己会找到。

Next,进入分发代理安全设置窗口:

20100826-15

点击红框内的按钮,进入设置窗口:

20100826-16

设置如上图,Connect to the Distributor处设置的是发布服务器的sa帐户。

OK, Next, Next, Next:

20100826-17

Next, Finish, Success:

20100826-18

备份数据库的订阅就建好了!

现在来瞧一瞧订阅服务器CNBlogsDB2上的用于复制的数据库CNBlogsDemo:

20100826-19

看!我们在发布服务器上建立的表CNBlogsTest复制过来了。

现在我们去发布服务器CNBlogsDB1上添加一条记录:

20100826-20

再去订阅服务器CNBlogsDB2瞧一瞧:

20100826-21

数据立即同步过来了!搞定!

20791975316932

遇到的问题:

在测试过程中被两个问题折腾了很长时间。

1)发布服务器的Log Reader Agent不能启动,错误信息:

· The process could not execute ‘sp_replcmds’ on ‘YCSERVER006’. (Source: MSSQL_REPL, Error number: MSSQL_REPL20011)
Get help: http://help/MSSQL_REPL20011
· Cannot execute as the database principal because the principal “dbo” does not exist, this type of principal cannot be impersonated, or you do not have permission. (Source: MSSQLServer, Error number: 15517)
Get help: http://help/15517
· The process could not execute ‘sp_replcmds’ on ‘YCSERVER006’. (Source: MSSQL_REPL, Error number: MSSQL_REPL22037)
Get help: http://help/MSSQL_REPL22037

开始测试时,附加了一个现有数据库进行复制遇到了这个问题,附加的是一下SQL Server 2005数据库文件,Owner为空,改为sa问题就解决了,如下图:

2)第二个问题就是前面已经描述过的订阅服务器访问发布服务器上的快照文件夹的问题,后来通过FTP的方式解决的。

对于SQL Server 2008数据库复制,目前我就学习了这些,期待园子里有这方面经验的朋友也来分享一下,在分享过程中你也会学到很多。

[转载]C#.NET发EMAIL的几种方法 MailMessage/SmtpClient/CDO.Message - 熊哥 www.relaxlife.net - 博客园

mikel阅读(880)

[转载]C#.NET发EMAIL的几种方法 MailMessage/SmtpClient/CDO.Message – 熊哥 www.relaxlife.net – 博客园.

C#.NET发EMAIL的几种方法 MailMessage/SmtpClient/CDO.Message

源代码如下:

using System; using System.Collections.Generic; using System.Text; using System.Web; using System.Net.Mail; using System.Net; namespace Pub.Class { /// <summary> /// 发送Email类 /// </summary> public class Email { #region 私有成员 private static object lockHelper = new object(); private string _From; private string _FromEmail; private string _Subject; private string _Body; private string _SmtpServer; private string _SmtpPort = "25"; private string _SmtpUserName; private string _SmtpPassword; private System.Web.Mail.MailFormat _Format = System.Web.Mail.MailFormat.Html; private System.Text.Encoding _Encoding = System.Text.Encoding.Default; #endregion #region 属性 /// <summary> /// 正文内容类型 /// </summary> public System.Web.Mail.MailFormat Format { set { _Format = value; } } /// <summary> /// 正文内容编码 /// </summary> public System.Text.Encoding Encoding { set { _Encoding = value; } } /// <summary> /// FromEmail 发送方地址(如test@163.com) /// </summary> public string FromEmail { set { _FromEmail = value; } } /// <summary> /// From /// </summary> public string From { set { _From = value; } } /// <summary> /// 主题 /// </summary> public string Subject { set { _Subject = value; } } /// <summary> /// 内容 /// </summary> public string Body { set { _Body = value; } } /// <summary> /// SmtpServer /// </summary> public string SmtpServer { set { _SmtpServer = value; } } /// <summary> /// SmtpPort /// </summary> public string SmtpPort { set { _SmtpPort = value; } } /// <summary> /// SmtpUserName /// </summary> public string SmtpUserName { set { _SmtpUserName = value; } } /// <summary> /// SmtpPassword /// </summary> public string SmtpPassword { set { _SmtpPassword = value; } } #endregion #region 构造器 /// <summary> /// 构造器 /// </summary> public Email() { } #endregion #region Send /// <summary> /// 发送EMAIL /// </summary> /// <example> /// <code> /// Email _Email = new Email(); /// _Email.FromEmail = "test@163.com"; /// _Email.Subject = "&lt;div>aaaa&lt;/div>"; /// _Email.Body = "aaaaaaaaaaaaa"; /// _Email.SmtpServer = "smtp.163.com"; /// _Email.SmtpUserName = "aaa"; /// _Email.SmtpPassword = "aaa"; /// _Email.Send("test@163.com"); /// </code> /// </example> /// <param name="toEmail">收信人 接收方地址</param> /// <returns>成功否</returns> public bool SmtpMailSend(string toEmail) { lock (lockHelper) { System.Web.Mail.MailMessage msg = new System.Web.Mail.MailMessage(); try { msg.From = _FromEmail;//发送方地址(如test@163.com) msg.To = toEmail;//接收方地址 msg.BodyFormat = _Format;//正文内容类型 msg.BodyEncoding = _Encoding;//正文内容编码 msg.Subject = _Subject;//主题 msg.Body = _Body;//内容 msg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", "1");//设置为需要用户验证 if (!_SmtpPort.Equals("25")) msg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport", _SmtpPort);//设置端口 msg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", _SmtpUserName);//设置验证用户名 msg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", _SmtpPassword);//设置验证密码 System.Web.Mail.SmtpMail.SmtpServer = _SmtpServer;//邮件服务器地址(如smtp.163.com) System.Web.Mail.SmtpMail.Send(msg);//发送 return true; } catch { } finally { } } return false; } /// <summary> /// 发送EMAIL /// </summary> /// <param name="toEmail">Email</param> /// <returns>是否成功</returns> public bool CDOMessageSend(string toEmail) { lock (lockHelper) { CDO.Message objMail = new CDO.Message(); try { objMail.To = toEmail; objMail.From = _FromEmail; objMail.Subject = _Subject; if (_Format.Equals(System.Web.Mail.MailFormat.Html)) objMail.HTMLBody = _Body; else objMail.TextBody = _Body; //if (!_SmtpPort.Equals("25")) objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserverport"].Value = _SmtpPort; //设置端口 objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserver"].Value = _SmtpServer; objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/sendusing"].Value = 1; objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout"].Value = 10; objMail.Configuration.Fields.Update(); objMail.Send(); return true; } catch {} finally{ } System.Runtime.InteropServices.Marshal.ReleaseComObject(objMail); objMail = null; } return false; } /// <summary> /// CDOMessageSend /// </summary> /// <param name="toEmail"></param> /// <param name="sendusing"></param> /// <returns></returns> public bool CDOMessageSend(string toEmail,int sendusing) { lock (lockHelper) { CDO.Message objMail = new CDO.Message(); try { objMail.To = toEmail; objMail.From = _FromEmail; objMail.Subject = _Subject; if (_Format.Equals(System.Web.Mail.MailFormat.Html)) objMail.HTMLBody = _Body; else objMail.TextBody = _Body; if (!_SmtpPort.Equals("25")) objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserverport"].Value = _SmtpPort; //设置端口 objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserver"].Value = _SmtpServer; objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/sendusing"].Value = sendusing; objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/sendemailaddress"].Value = _FromEmail; objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/smtpuserreplyemailaddress"].Value = _FromEmail; objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/smtpaccountname"].Value = _SmtpUserName; objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/sendusername"].Value = _SmtpUserName; objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/sendpassword"].Value = _SmtpPassword; objMail.Configuration.Fields["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"].Value=1; objMail.Configuration.Fields.Update(); objMail.Send(); return true; } catch { } finally{ } System.Runtime.InteropServices.Marshal.ReleaseComObject(objMail); objMail = null; } return false; } /// <summary> /// SmtpClientSend /// </summary> /// <param name="toEmail"></param> /// <returns></returns> public bool SmtpClientSend(string toEmail) { lock (lockHelper) { System.Net.Mail.MailMessage message = new MailMessage(_FromEmail, toEmail, _Subject, _Body); message.SubjectEncoding = _Encoding; message.BodyEncoding = _Encoding; message.IsBodyHtml = true; message.Priority = MailPriority.High; SmtpClient client = new SmtpClient(_SmtpServer); client.UseDefaultCredentials = false; client.Credentials = new NetworkCredential(_SmtpUserName, _SmtpPassword); client.DeliveryMethod = SmtpDeliveryMethod.Network; client.Port = Str.ToInt(_SmtpPort, 587); client.EnableSsl = true; try { client.Send(message); } catch { return false; } return true; } } #endregion } }

多线程服务调用:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration.Install; using System.ServiceProcess; using System.Collections; using System.Threading; using System.Xml; using System.IO; using System.Net.Mail; using System.Runtime.Remoting.Channels.Tcp; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting; using Pub.Class; using System.Diagnostics; namespace MailService { [RunInstaller(true)] public partial class MService : ServiceBase { public static bool isRun = false; public Queue emailQueue = new Queue(); private Thread readEmailThread; private Thread[] sendEmailThread; private string[] strList = new string[] { "MailService 启动成功!", "MailService 停止!", "{2} {1} - [{0}] - 发送失败!", "{2} {1} - [{0}] - 发送成功!", "LiveRemotingService 已启动,服务端口6669。", "LiveRemotingService 停止!" }; private struct Config { public string Conn; public string LogFile; public string SmtpServer; public string UserName; public string Password; public string FromAddress; public int AmountThread; public int RecordCount; public int TimeInterval; } private Config config = new Config(); public MService() { System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false; InitializeComponent(); if (!System.Diagnostics.EventLog.SourceExists("MailSource")) System.Diagnostics.EventLog.CreateEventSource("MailSource", "MailServiceLog"); this.eventLog1.Source = "MailSource"; this.eventLog1.Log = "MailServiceLog"; this.eventLog2.Source = "LiveRemotingSource"; this.eventLog2.Log = "MailServiceLog"; } protected override void OnStart(string[] args) { try { InitConfig(); this.eventLog1.WriteEntry(strList[0], System.Diagnostics.EventLogEntryType.SuccessAudit); this.timer1.Interval = config.TimeInterval * 1000; this.timer1.Enabled = true; sendEmailThread = new Thread[config.AmountThread]; } catch (Exception e) { this.eventLog1.WriteEntry(e.ToString(), System.Diagnostics.EventLogEntryType.Error); } } protected override void OnStop() { this.eventLog1.WriteEntry(strList[1], System.Diagnostics.EventLogEntryType.SuccessAudit); GC.Collect(); this.timer1.Enabled = false; } private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (isRun) return; timer1.Enabled = false; readEmailThread = new Thread(new ThreadStart(ReadEmailQuque)); readEmailThread.IsBackground = true; readEmailThread.Start(); } private void InitConfig(){ config.Conn = Pub.Class.WebConfig.GetApp("ConnString"); config.LogFile = Pub.Class.WebConfig.GetApp("logFile"); config.SmtpServer = Pub.Class.WebConfig.GetApp("SmtpServer"); config.UserName = Pub.Class.WebConfig.GetApp("UserName"); config.Password = Pub.Class.WebConfig.GetApp("Password"); config.FromAddress = Pub.Class.WebConfig.GetApp("FromAddress"); string amountThread = Pub.Class.WebConfig.GetApp("AmountThread"); config.AmountThread = amountThread.Equals("") ? 1 : Convert.ToInt32(amountThread); config.AmountThread = config.AmountThread < 1 ? 1 : config.AmountThread; string recordCount = Pub.Class.WebConfig.GetApp("RecordCount"); config.RecordCount = recordCount.Equals("") ? 1000 : Convert.ToInt32(recordCount); config.RecordCount = config.RecordCount < 1000 ? 1000 : config.RecordCount; string timeInterval = Pub.Class.WebConfig.GetApp("TimeInterval"); config.TimeInterval = timeInterval.Equals("") ? 1000 : Convert.ToInt32(timeInterval); config.TimeInterval = config.TimeInterval < 2 ? 2 : config.TimeInterval; } private void ReadEmailQuque(){ timer1.Enabled = true; IList<EC_EmailList> list = EC_EmailListFactory.Instance().SelectListByTop(config.RecordCount); if (list.Count == 0) return; isRun = true; for (int i = 0; i < list.Count; i++) { emailQueue.Enqueue(list[i]); } for (int i = 0; i < config.AmountThread; i++) { sendEmailThread[i] = new Thread(new ThreadStart(DoSendEmail)); sendEmailThread[i].Name = "Thread" + (i+1).ToString(); sendEmailThread[i].Start(); } list = null; } private void DoSendEmail(){ while (true) { EC_EmailList objMail; lock(this){ if (emailQueue.Count>0) { objMail = (EC_EmailList)emailQueue.Dequeue(); } else { isRun = false; return; } } int mailID = (int)objMail.EmailID; string strTo = objMail.To; string strSubject = objMail.Subject; string strBody = objMail.Body; string strFrom = objMail.From; string smtpServer = objMail.SmtpServer; string userName = objMail.UserName; string password = objMail.Password; bool isTrue = SendMail(strTo, strSubject, strBody, strFrom, smtpServer, userName, password, ""); EC_EmailListFactory.Instance().DeleteByID(mailID); } } public bool SendMail(string strTo, string strSubject, string strBody, string strFrom, string smtpServer, string userName, string password, string attachments) { Email email = new Email(); string strSmtpServer = smtpServer.Length > 0 ? smtpServer : config.SmtpServer.Trim(); email.SmtpServer = strSmtpServer; email.SmtpUserName = userName.Length > 0 ? userName : config.UserName.Trim(); email.SmtpPassword = password.Length > 0 ? password : config.Password.Trim(); email.SmtpPort = strSmtpServer.ToLower().Contains("gmail") ? "587" : "25"; email.EnableSsl = strSmtpServer.ToLower().Contains("gmail") ? true : false; email.FromEmail = strFrom.Length > 0 ? strFrom : config.FromAddress.Trim(); email.Subject = strSubject; email.Body = strBody; email.Encoding = System.Text.Encoding.UTF8; bool isSuccess = email.SmtpClientSend(strTo); return isSuccess; } public void ErrorLog(string strMessage) { lock(this){ StreamWriter sw = new StreamWriter(config.LogFile + "MailLog.txt", true); sw.WriteLine(strMessage); sw.Flush(); sw.Close(); } } } }

曾经运行在MSN的MCLUB的服务器上跑发EMAIL的服务。应该是安全无死锁调用。

[转载]ASP.NET MVC路由匹配检测组件RouteDebug.dll

mikel阅读(1173)

[转载]ASP.NET MVC路由匹配检测组件RouteDebug.dll – Capricornus – 博客园.

以前使用RouteMonitor.dll进行MVC路由检测URL路径的映射匹配情况。由于公司电脑没有此组件,所以上网搜了下,结果才发现RouteMonitor.dll已经将名称改为了RouteDebug.dll 。具体参阅 官方网站。 下载地址:http://files.cnblogs.com/Capricornus/RouteDebug-Binary.zip

使用方法:

1. 在MVC项目中添加引用此组件

2. 在全局应用程序类Global.asax.cs中设置代码

3.匹配路由如下图:


我们可以使用Reflector反编译这个RouteDebugger.dll组件,查看一下原理。如图:

RouteDebug中包含了DebugHttpHandler、DebugRoute、DebugRouteHandler、RouteDebugger这4个类。

首先从我们调用RouteDebug.RouteDebugger.RewriteRoutesForTesting的着手。


RouteDebugger类:

首先,整个代码是使用System.Web.Routing命名空间下的RouteCollection.GetReadLock()锁定的,提供 一个对象,用于管理在从集合中检索对象时的线程安全性;然后遍历我们传过来的路由集合参数。用RouteDebug中的 DebugRouteHandler去替换原有RouteHandler,以便改变Http处理程序的方向,接着将Singletion属性的值添加到路 由结合中。


DebugRoute类:

DebugRoute继承与Route类,构造函数实现了构造可捕获所有URL地址的Route。


DebugRouteHandler路由处理程序类:

实现IHttpHanlder接口的实例化对象,传入了一个RequestContext对象实例。


DebugHttpHandler类:

通过ProcessRequest方法来处理请求,最后呈现在路由检测的页面上。

首先从RequestContext.RouteData可以得到RouteData类,RouteData类包含所请求路由的相关值。从 RouteData.Values获取路由的URL参数值和默认值集合,在从RouteData.Route获取路由的对象,在获取有关集合中与指定值匹 配的路由信息.

[转载]w3wp进程发生死锁ISAPI '..\aspnet_isapi.dll' 报告它自身有问题,原因'Deadlock detected'

mikel阅读(1178)

[转载]w3wp进程发生死锁ISAPI ‘..\aspnet_isapi.dll’ 报告它自身有问题,原因’Deadlock detected’ – 欢迎光临赵玉开的技术博客 – 博客园.

ISAPI ‘c:\windows\microsoft.net\framework\v2.0.50727\aspnet_isapi.dll’ 报告它自身有问题,原因如下: ‘Deadlock detected’

这个问题,字面意思是程序发生死锁了,它会导致w3wp进程重启。通常这个问题不好查到原因。我知道两个可能导致此问题的实例

1. 在程序中使用了lock或者ReaderWriterLock,锁资源发生了争用
下面是一小段代码:

1 //_rwLock的类型是ReaderWriterLock
2 _rwLock.AcquireWriterLock(100);
3 DoSomething();
4 _rwLock.ReleaseWriterLock();

这行代码是有问题的,如果在DoSomething()方法执行中发生一次异常,这个写锁就释放不了了,再次请求时就会等待直到超时,在多线程的情况下就会发生死锁’Deadlock detected’
正确的写法应该是:

01 try
02 {
03 _rwLock.AcquireWriterLock(100);
04 DoSomething();
05 }
06 finally
07 {
08 if (_rwLock.IsWriterLockHeld)
09 _rwLock.ReleaseWriterLock();
10 }

这样就算在DoSomething方法执行时发生了异常,也可以释放写锁。

2. 数据库连接的超时时间设置的很长而在设定的超时时间之内连接耗尽了,再次要求打开数据库连接时也可能会出现此问题。这个是数据库连接串的配置问题,超时时间要设置的适当,不要过长。

发生这个问题时的日志写的很笼统:
ISAPI ‘c:\windows\microsoft.net\framework\v2.0.50727\aspnet_isapi.dll’ 报告它自身有问题,原因如下: ‘Deadlock detected’。

有关更多信息,请参阅在 http://go.microsoft.com/fwlink/events.asp 的帮助和支持中心。

这样导致不容易找到问题发生在哪块,所以我记录两种发生此问题的实例,希望有用。

[转载]ASP.NET MVC Routing概述 (C#)

mikel阅读(952)

[转载]ASP.NET MVC Routing概述 (C#) – JasenKin – 博客园.

                                           ASP.NET MVC Routing概述

ASP.NET Routing模块的责任是将传入的浏览器请求映射为特有的MVC controller actions。

使用默认的Route Table
当你创建一个新的ASP.NET MVC应用程序,这个应用程序已经被配置用来使用ASP.NET Routing。 ASP.NET Routing 在2个地方设置。第一个,ASP.NET Routing 在你的应用程序中的Web配置文件(Web.config文件)是有效的。在配置文件中有4个与routing相关的代码片 段:system.web.httpModules代码段,system.web.httpHandlers 代码段,system.webserver.modules代码段以及 system.webserver.handlers代码段。千万注意不要删除这些代码段,如果没有这些代码段,routing将不再运行。第二个,更重 要的,route  table在应用程序的Global.asax文件中创建。这个Global.asax文件是一个特殊的文件,它包含ASP.NET 应用程序生命周期events的event handlers。这个route  table在应用程序的起始event中创将。

       在Listing 1中包含ASP.NET MVC应用程序的默认Global.asax文件.

Listing 1 – Global.asax.cs

 1 public class MvcApplication : System.Web.HttpApplication
 2     {
 3         public static void RegisterRoutes(RouteCollection routes)
 4         {
 5             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 6             routes.MapRoute(
 7                 "Default", // 路由名称
 8                 "{controller}/{action}/{id}", // 带有参数的 URL
 9                 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值
10             );
11
12         }
13
14         protected void Application_Start()
15         {
16             AreaRegistration.RegisterAllAreas();
17
18             RegisterRoutes(RouteTable.Routes);
19         }
20     }

当一个MVC应用程序第一个启动,Application_Start() 方法被调用,这个方法反过来调用RegisterRoutes() 方法。

这个默认的route table包含一个单一的route。这个默认的route将url的第一个段映射为一个controller名称,url的第二个段映射为一个controller action,第三个段映射为命名为id的参数。
假如,你在网页浏览器的地址栏中键入下面的url:/Home/Index/3,这个默认的route将这个url映射为下面的参数:
controller = Home controller名称

action = Index controller action

id = 3 id的参数

当你请求/Home/Index/3这样的url,下面的代码将执行。HomeController.Index(3)

这个默认的route包含3个默认的参数。如果你没有提供一个 controller,那么 controller默认为Home。同样,action默认为Index,id参数默认为空字符串。
让我们来看一些关于默认的route怎么映射urls为controller actions的例子。假如你在你的浏览器地址栏中输入如下的url:/Home, 由于这些默认的route参数有一些相关的默认值,键入这样的URL,将导致HomeController类的Index()方法(如Listing 2)被调用。

1 namespace MvcRoutingApp.Controllers
 2 {
 3     [HandleError]
 4     public class HomeController : Controller
 5     {
 6         public ActionResult Index(string id)
 7         {
 8             ViewData["Message"] = "欢迎使用 ASP.NET MVC!";
 9
10             return View();
11         }
12
13         public ActionResult About()
14         {
15             return View();
16         }
17     }
18 }
19

在Listing 2中,这个HomeController 类包含一个名为Index()的方法。这个URL /Home导致Index()方法被调用,一个空的字符串将作为id参数的值。由于mvc框架调用controller actions的这种方式,这个URL /Home同样匹配HomeController类中的Index()方法(如Listing 3)。
Listing 3 – HomeController.cs (Index action with no parameter)

[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}

在Listing 3中,这个Index()方法不接收任何参数。这个URL /Home将导致Index()方法被调用。URL /Home/Index/3同样调用这个方法(ID被忽略)。
Listing 4 – HomeController.cs (Index action with nullable parameter)
[HandleError]
public class HomeController : Controller
{
public ActionResult Index(int? id)
{
return View();
}
}

在Listing 4中, Index() 方法有一个整数参数. 由于这个参数是可空参数 ,  Index() 将被调用而不引起错误.

最后, 使用 URL /Home 来调用如Listing 5中的Index() 方法 将导致异常,因为这个ID参数不是一个可空的参数。如果你试图去调用这个Index()方法,你将获得如下图所示的错误。

Listing 5 – HomeController.cs (Index action with Id parameter)
[HandleError]
public class HomeController : Controller
{
public ActionResult Index(int id)
{
return View();
}
}

另一方面,使用如Listing 5中的Index controller action,URL /Home/Index/3运行正常。Index controller action in Listing 5. /Home/Index/3请求将导致Index()方法被调用,ID参数拥有一个3的值。

总结


这是一个关于ASP.NET Routing的简要介绍. 应该了解了这个默认的route如何将URLs映射为controller actions。
创建自定义的Routes (C#)

这个教程,你将学会怎样添加一个自定义的route到一个ASP.NET MVC应用程序。你将学会在Global.asax文件中,怎样使用一个自定义的route来修改这个默认的route table。
对于许多简单的ASP.NET MVC 应用程序,这个默认的route  table将运行得很好。然而,你可能发现,你可能特定的routing 需求。那样的话,你可能需要创建一个自定义的route。
设想一下,例如,你正在建立一个博客应用程序,你可能想要去处理像/Archive/12-25-2009的输入请求。
当一个用户键入这个请求,你想要返回与日期为12/25/2009相符的博客实体。为了处理这种类型的请求,你需要去创建一个自定义的route。
在 Listing 1中,这个Global.asax文件中包含一个新的名为Blog的自定义route,它处理类似于/Archive/entry date的请求。
Listing 1 – Global.asax (with custom route)

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Web;
 5 using System.Web.Mvc;
 6 using System.Web.Routing;
 7 
 8 namespace MvcRoutingApp
 9 {
10     // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明,
11     // 请访问 http://go.microsoft.com/?LinkId=9394801
12 
13     public class MvcApplication : System.Web.HttpApplication
14     {
15         public static void RegisterRoutes(RouteCollection routes)
16         {
17             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
18             routes.MapRoute(
19                 "Blog", // 路由名称
20                 "Archive/{entryDate}/{id}", // 带有参数的 URL
21                 new { controller = "Archive", action = "Entry", id = UrlParameter.Optional } // 参数默认值
22             );
23             routes.MapRoute(
24                 "Default", // 路由名称
25                 "{controller}/{action}/{id}", // 带有参数的 URL
26                 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值
27             );
28 
29         }
30 
31         protected void Application_Start()
32         {
33             AreaRegistration.RegisterAllAreas();
34 
35             RegisterRoutes(RouteTable.Routes);
36         }
37     }
38 }

你添加到route table的routes的顺序是很重要的。我们新自定义的blog route在现存的默认route之前添加。如果你颠倒了顺序,那么这个默认的route总是先调用而不是这个自定义的route。
这个自定义的blog toute匹配任何以 /Archive/ 开头的请求。所以,它匹配所有下列URLs:
/Archive/12-25-2009

/Archive/10-6-2004

/Archive/apple

这个自定义的route将输入的请求映射至名为Archive的controller,并且调用 Entry() action。当 Entry() action被调用的时候,这个输入的日期被当作名为entryDate的参数。
Listing 2 – ArchiveController.cs
public class ArchiveController : Controller
{

public string Entry(DateTime entryDate)
{
return “You requested the date:” + entryDate.ToString();
}

}

注意,在Listing 2中这个Entry()方法接收一个类型为DateTime的参数。MVC框架是足够智能的,它自动将URL中输入的date转换为一个DateTime值。如果URL中输入的date不能转换为DateTime,错误将被引发。

总结
这个教程演示怎样来创建一个自定义的route。你学会了怎样在Global.asax 文件中添加一个自定义的route到route table。我们讨论了怎样为blog实体将请求映射为名为ArchiveController的controller,名为Entry()的controller action。

[转载]c# 中内部类的简单介绍

mikel阅读(1075)

[转载]c# 中内部类的简单介绍 – 情缘 – 博客园.

最近在看java一方面的书籍,看到一个很奇怪的问题,java类中还可以再定义一个类,这种结构非常特殊!后来才发现我知识浅薄了,原来C#中也有内部类,之前都一直没有注意过这个语法结构!

使用内部类有这样几个好处:

(1)抽象外部类的某一状态下的行为,隐藏实现,通过修改该内的访问修饰符,可以设置仅有外部类可以访问该类

(2)扩展了命名空间,可以将外部类的类名作为内部类的一个命名空间(这里只是相当于,但不是真正的命名空间)

(3)内部类可以当作外部类的一个扩展,可以活的更好的封装。

上面的这些特点胡乱的总结了一下,可能有些词不达意,下面有些具体例子:

1.内部类的定义:

嵌套类:在一个类中定义另外一个类,主要分为静态嵌套类和非静态嵌套类(又称之为”内部类”)

内部类的定义结构:(1)在一个类中直接定义类(2)在一个方法中定义类(3)匿名内部类

2.外部类访问内部类

外部类访问内部类 例子

该段代码定义了一个外部类Person 和一个内部类Student, 其中内部类Student中使用了各种修饰符修饰的变量和方法,从上面的例子可以看出外部类只能够访问嵌套类中修饰符为public、internal的字段、方法、属性。

调用外部类的 Show()方法运行得到如下结果:

3.内部类访问外部类

内部类访问外部类 例子

1 namespace GameStatistical.Test.InnerClass
2 {
3 public class Person1
4 {
5 private string name;
6
7 public string Name
8 {
9 get { return name; }
10 set { name = value; }
11 }
12 private string sex;
13
14 public string Sex
15 {
16 get { return sex; }
17 set { sex = value; }
18 }
19
20 public void Show1()
21 {
22 Console.WriteLine(this.name + ==> + this.sex);
23 }
24
25 private static void Show2()
26 {
27 Console.WriteLine(===================>);
28 }
29
30 internal void Show3()
31 {
32 Console.WriteLine(this.name + ==> + this.sex);
33 }
34
35
36
37 public class Student
38 {
39 public void SetPer(string name, string sex)
40 {
41 Person1 p = new Person1();
42 p.name = name;
43 p.sex = sex;
44
45 p.Show3();
46 p.Show1();
47 }
48
49 }
50 }
51 }

这段代码同样定义了一个外部类Person1 和一个内部类Student,内部类中的SetPer()调用了外部类中的方法,写这段代码我们可以发现 嵌套类可以访问外部类的方法、属性、字段而不受访问修饰符的限制

4.内部类的继承

内部类继承例子1

1 namespace GameStatistical.Test.InnerClass
2 {
3 public class Person
4 {
5 public class Student
6 {
7 public static int age;
8 internal static int height;
9 private static string sex;
10
11 public virtual void Show()
12 {
13 Console.WriteLine(年龄:+age);
14 Console.WriteLine(身高:+height);
15 }
16
17 internal void Display()
18 {
19 Console.WriteLine(internal);
20 Console.WriteLine(年龄: + age);
21 Console.WriteLine(身高: + height);
22 }
23 }
24
25
26 public void Show()
27 {
28 Student.age = 21;
29 Student.height = 75;
30 Student student = new Student();
31 student.Show();
32 student.Display();
33 }
34 }
35 }

内部类继承,上面的内部类定义了父类,其中public virtual void Show() 使用virtual 修饰,可以用于子类重写这个方法,看内部类继承子类是否能够重写这个方法。

内部类继承例子2

1 namespace GameStatistical.Test.InnerClass
2 {
3 public class SubPerson:Person
4 {
5 public class SubStudent : Student
6 {
7 public override void Show()
8 {
9 base.Show();
10 }
11 }
12 }
13 }

上面的代码重写了Show() 这个方法,说明内部类的继承可以通过

5.反射内部类

对于这段代码,是从其他网站看到的,反射内部类我们不能直接通过 “.” 操作符直接来操作,而是通过 “+” 操作符。前面也提到过内部类也是一种有效的管理命名空间的方法,这里也是普通类和内部类的一点区别

反射内部类

Activator.CreateInstance(“GameStatistical.Test.InnerClass”, “GameStatistical.Test.InnerClass.ReflectionPerson+Student”);

反射普通类

Activator.CreateInstance(“GameStatistical.Test.InnerClass”, “GameStatistical.Test.InnerClass.ReflectionPerson.Student”);

在实际操作中,内部类好像使用的比较少,这里也只是非常简单的介绍,作为一个知识点总结起来。

[转载]JqueryNet—NJquery

mikel阅读(1125)

[转载]JqueryNet—NJquery – NSun快速开发 – 博客园.

Jquery可以说是非常成功的js框架,简单的操作就可以完成复杂的DOM操作。对DOM对象操作方法的封装是它成功的把很多对

象的共性提取转化为Jquery对象的通用操作方法。比如:所有对象的属性操作都可以用attr来完成对象的样式操作可以用css这

样的方法来完成,提取value可以用val方法来完成,隐藏了不同DOM对象的操作形式大大简化了操作的复杂度。

 

      介绍了Jquery为我们带来的方便,那么介绍下jquerynet。顾名思义,jquerynet是基于net以jquery的方式来实现操作,

这里操作的不是DOM对象,而是我们熟悉的asp.net服务器控件,服务器控件一般常见的有2种形式,一种是Htmlcontrol另一

种则是Webcontrol他们都是继承与Control,如果前端人员在其他编辑器中放置好表单元素后我们在控件属性加runat=”server” 

那么这个控件则变为htmlcontrol,当然普通的服务器控件则为webcontrol,有些则直接继承自control。jquerynet是封装这些

控件的操作方法,比如样式、属性、取值、事件。把这些相关的操作实现统一化隐藏他们的差异。当然这样的想法很早就有了,

一直没有去实现它。查过相关的资料,也没有找到相关的项目以及实现。
JqueryNet.JQuery.Get(a).Click("a()");

var c = JqueryNet.JQuery.Get(TextArea1).Val();
Response.Write(c.Value);
var c = JqueryNet.JQuery.Get(show).Children<HtmlInputControl>(p => p.Type == "checkbox");
foreach (var item in (List<HtmlInputControl>)c.Value)
{
     Response.Write(item.ID);
}

JqueryNet.JQuery.Get(FileUpload1).Val(Server.MapPath("~/Scripts") + "//" + FileUpload1.FileName);

var query = JqueryNet.JQuery.Get(Button4);
query.AddClass("t");
query.Height("200px");

JqueryNet.JQuery.Get(Select1).Val().Value);

同样支持方法连写:

JQueryNet.JQuery.Get(Button4).AddClass(“t”).Height(“200px”);

以上为相关的服务器控件操作。如果有兴趣的朋友希望与我联系。

实现比较繁琐,一步步改进中。。

Code URL:http://njquery.codeplex.com

Subversion URL: https://njquery.svn.codeplex.com/svn

[转载]理解MVC应用程序的执行过程

mikel阅读(1184)

[转载]理解MVC应用程序的执行过程 – JasenKin – 博客园.

基于ASP.NET MVC Web应用程序的请求首先通过一个UrlRoutingModule的对象(HTTP模块)。这个模块匹配请求,并且执行路由选择。这个 UrlRoutingModule对象选择第一个匹配当前请求的路由对象。如果没有路径匹配,这个UrlRoutingModule什么也不做,让这个请 求返回给常规的ASP.NET或者IIS来请求处理。

从这个被选中的Route对象,UrlRoutingModule对象获得IRouteHandler对象(IRouteHandler对象与Route 对象是相互关联的)。一般来说,在一个MVC应用程序中,它将是MvcRouteHandler实例。这个IRouteHandler实例创建一个 IHttpHandler对象,并且将它传递给IHttpContext对象。默认情况下,MVC IHttpHandler实例就是MvcHandler对象。然后,这个MvcHandler对象选择controller,controller将最终 提交这个请求。
这个module 和 handler是ASP.NET MVC框架的入口点。它们执行下列行为:

选择合适的controller。
获得一个具体controller实例。
调用controller的执行方法。

下表列出了一个MVC Web项目的执行的各阶段。

阶段 详细
接收应用程序的第一次请求 在Global.asax文件中, Route对象 被添加到RouteTable对象.
执行路由选择 UrlRoutingModule 模块使用第一个在RouteTable 集合中匹配的Route 对象来创建RouteData对象, 然后它将使用这个RouteData对象来创建RequestContext (IHttpContext)对象.
创建MVC request handler MvcRouteHandler 创建MvcHandler类的一个实例,并且将它传递给RequestContext实例.
创建controller MvcHandler对象使用RequestContext实例来确认IControllerFactory 对象(DefaultControllerFactory类的一个实例) ,以用来创建conteoller实例。
执行controller MvcHandler 实例调用controller的执行method.
调用action 大部分controllers 继承自Controller基础类. 与controller相关联的ControllerActionInvoker 对象决定这个controller类的哪个方法将被调用 , 然后再调用那个方法.
执行result 一个典型的action 方法可能接收用户输入,准备合适的响应数据, 然后通过返回一个result的类型来执行这个result. 这个内置的能够执行的result 类型 包含以下类型: ViewResult (它呈现一个视图,并且是最常用的result类型), RedirectToRouteResult, RedirectResult, ContentResult, JsonResult以及EmptyResult.

[转载]工欲善其事必先利其器-简单几步打造顺手的python开发工具(windows,Linux多版本)

mikel阅读(981)

[转载]工欲善其事必先利其器-简单几步打造顺手的python开发工具(windows,Linux多版本) – 懒人居 – Coding for fun – 博客园.

初学一门语言,一般来说第一道门槛是开发工具的选择,以及配置,用记事本写代码写写helloworld尚堪一用,但是一旦需要进行稍微规模大一点的开发立马就捉襟见肘了,所以本文的目的在于给打算学习Python的Tx一个快速入手的指南。

方法零,用EditPlus或者UtralEdit等加强版记事本,语法高亮,ok,但是自动补全,智能感知等就不要想了,杯具,据说某些对IDE有强烈怨念的大牛喜欢这种完全反IDE的方法来开发

方法一,如果你正在使用Eclipse,那么恭喜你,你只需要安装PyDev这个插件就可以了,这个插件可以在

http://pydev.org/ 获得,你也可以通过将urlhttp://pydev.org/updates 加入Eclipse的UpdateManager来安装这个插件。这个插件的特征列表可以在 http://pydev.org/manual_adv_features.html 这个地方找到,功能很全不过智能感知的功能经常抽筋,所以很多时候还是要靠你自己了。

方法二,这个方法我没试过,据说,据说VS.NET直接装上IronPython就可以当作Python的IDE,嗯,有用过的同学可以回复告诉我们使用体验,

方法三 ,windows下的同学可以下载NetBeans的python earlyaccess版本,或者JetBrains的PyCharm(此物据说是测试版,正式版本可能会收费)

方 法四,如果你对全功能的,除了生娃哇什么都能替你完成的superIDE厌倦了,想要更加轻便的,轻巧的,不会动辄好几百M的庞大身躯,一启动好几百M上 G的内存消耗的话,那么一些轻便的IDE可能会更适合你,比如windows下可以用,Ulipad(推荐,国产,Python社区Limodou大牛力 作) ,KomodoEdit(阉割版 KomodoIDE),经过我的试用就这两个比较好用,其余的比如Eric,太复杂了,WindIDE因为要钱所以压根就没看过,SPE到处都是作者要捐 赠的广告-囧。

Ulipad相对来说很好安装,你如果装了WxPython的话。只需要 svn checkout http://ulipad.googlecode.com/svn/trunk/ ulipad-read-only 就可以签出最新版的源代码了,运行只需要 python UliPad.py即可(经过实验,Ulipad在linux下运行会有一些怪异的情况出现,建议在Windows下使用)

KomodoEDIT相对来说麻烦一点,大家可以到官网 http://www.activestate.com/komodo-edit 去下载,这个东西是基于Mozilla的框架很是新鲜,用浏览器做外壳的编辑器,可以和Firefox一样安装插件

方 法五, 如果你懒得连IDE都不想去下载安装,并且是疯狂的DIY分子,那么可以尝试用强力的编辑器自己改装成自己独一无二的酷IDE,代码高亮,自动补全,智能 感知,我所期待的功能一个不少。那么适合的编辑器有超能的Emacs,vim还有图形界面的Gedit。由于Emacs用Lisp做插件,这个我还很 弱,vim本身的命令式编辑我还在适应过程中,所以剩下的就是很有亲和力的Gedit了,这个编辑器是Ubuntu的桌面环境自带的文本编辑器,虽然你刚 装好Ubuntu后打开它看着和记事本一样简洁,但是这个家伙绝对不简单。所以最后我来给大家展示一下用Gedit来构建Python开发工具的过程。

首先,假设我们刚装好了Ubuntu,这个时候在应用程序->附件->文本编辑 就可以打开Gedit了,太简单了,简直和记事本一模一样嘛,如下图:

接下来我们要做的就是安装Gedit的官方插件包,方法是在终端窗口中输入 sudo apt-get install  gedit-plugins

然后我们就可以开始对Gedit进行个性化的配置了,打开  编辑->首选项,如下图:

默认情况下只启动了自动换行,我在这里把所有的勾都打上

然后点击编辑器,进行下一步配置:

在这里我们需要把默认关闭的自动缩进打开,并且将默认的 空格替代制表符的设置从8改成4,文件保存就不用选了,自动备份对svn工作目录下开发会造成很大的困扰的,这个要记得关掉。然后点击字体和颜色:

图里选择的Darkmate的配色方案是我在Gedit的官网下载的,可以把Gedit的编辑区伪造成TextMate的样式,呵呵 ,安装方法很简单,点击添加,找到下载的配色方案xml文件就行了,接下来选插件,进入功能配置

这 里直接把所有的勾上就行了,然后在菜单里就出现了对应的功能,有的作用于编辑区的就会在对应于特定的文件格式自动启动。 这个时候Gedit具备了,代码高亮,自动完成,自动补全还有一堆其他的编辑器功能,但是现在的智能感知不能根据上下文,而是根据当前文件或者打开文件中 的Token来匹配的,为了追求完美的效果,我在Gedit的官网上找到了一个正在开发中的插件,也就是Python 的智能感知插件,这个东西现在还在源码状态,所以只有在Github上去Clone下来,我打了个包放在本文结尾,需要的同学可以自己下载。这个插件安装 很简单,首先开一个终端。然后进入这个目录:

看到那个plugins目录了没,如果没有这个目录就mkdir plugins。如果有就把下载的插件cp进去就行了。cp进去了后重新打开Gedit,在插件那个地方就能看到这个插件了,到哪里打上勾就能用了。

最后再开启侧栏和底栏后我们来看看成品:

插件下载地址:/Files/Alexander-Lee/geditpycompletion_src.tar.gz.zip 由于不能上传.tar.gz的文件所以请在下载后把.zip去掉再解压