什么是事务处理:
事务是一组组合成逻辑工作单元的数据库操作,虽然系统中可能会出错,但事务将控制和维护每个数据库的一致性和完整性。
如果在事务过程中没有遇到错误,事物中的所有修改都将永久成为数据库的一部分。
如果遇到错误,则不会对数据库做出任何修改。
事务处理过程:
1.开始一个事务。进入“事物待命”状态。
2.在“事务待命”状态,记录事务中改变的数据库记录。此改变不能直接改变数据库中的值,必须先用一个顺序的“事务日志”记录在一边。同时,对于要改变的原始记录加锁,让其它用户无法读和写。如果记录已经被其它事务加锁,则报错。
3.在“事务待命”,如果用户给出commit transaction命令,则进入“事务拷贝”状态,拷贝所有的加锁的记录成备份。
4.上面3执行完,则进入“事务更新”状态,用“事务日志”中记录一一更新实际的数据库记录。
5.上面4执行完,则进入“事务结束”状态,释放所有的记录锁,然后抛弃“事务日志”和备份的原始数据库记录。
6.上面5做完后,事务被删除。
但是,最为关键的是,事务系统必须执行以下过程:一但数据库由于软件、硬件问题发生故障,重启动后,一旦有事务没有正常删除,则:
7.如果在“事务待命”、“事务结束”状态,则重新从5中结束事务的动作开始执行。
8.如果在“事务更新”状态,则重新从4开始更新记录,但继续想下执行。结果,虽然系统崩溃过,但事务仍然能正常提交。
事务处理相关事项:
事务处理的关键是在提交事务或者取消事务时,万一系统崩溃了,数据库再次启动时,仍然需要保持数据库逻辑一致性。
应用中包含的事务应当尽量让它“瞬间”完成,避免在比较忙时造成用户进程的互锁。
Informix、Oracle、DB2等数据库德实际事务处理流程更复杂,目的是具有更好的抵抗系统错误性质,因为事务保护是业务系统安全稳定的最后一道防线。
事务处理是在数据处理时经常遇到的问题,经常用到的方法有以下三种总结整理如下:
方法1:直接写入到sql 中
在存储过程中使用 BEGIN TRANS, COMMIT TRANS, ROLLBACK TRANS 实现
begin trans
declare @orderDetailsError int,@procuntError int
delete from [order details] where productid=42
select @orderDetailsError =@@error
delete from products where productid=42
select @procuntError=@@error
if(@orderDetailsError =0 and @procuntError=0)
COMMIT TRANS
else
ROLLBACK TRANS
优点:
所有事务逻辑包含在一个单独的调用中
拥有运行一个事务的最佳性能
独立于应用程序
限制:
事务上下文仅存在于数据库调用中
数据库代码与数据库系统有关
方法2 :使用ADO.Net 实现
使用ADO.Net 实现,使用这种方式的优点是可以在中间层来管理事务,当然你也可以选择在数据层来实现。
SqlConnection 和OleDbConnection 对象有一个 BeginTransaction 方法,它可以返回 SqlTransaction
或者OleDbTransaction 对象。而且这个对象有 Commit 和 Rollback 方法来管理事务
SqlConnection sqlConnection = new SqlConnection("workstation id=WEIXIAOPING;packet size=4096;user id=sa;initial catalog=Northwind;persist security info=False");
sqlConnection.Open();
SqlTransaction myTrans = sqlConnection.BeginTransaction();
SqlCommand sqlInsertCommand = new SqlCommand();
sqlInsertCommand.Connection = sqlConnection
sqlInsertCommand.Transaction=myTrans;
try{
sqlInsertCommand.CommandText="insert into tbTree(Context,ParentID) values('北京',1)";
sqlInsertCommand.ExecuteNonQuery();
sqlInsertCommand.CommandText="insert into tbTree(Context,ParentID) values('上海',1)";
sqlInsertCommand.ExecuteNonQuery();
myTrans.Commit();
}catch(Exception ex)
{
myTrans.Rollback();
}
finally
{
sqlConnection.Close();
}
优点:
简单性
和数据据事务差不多的快
独立于数据库,不同数据库的专有代码被隐藏了
缺点:
事务不能跨越多个数据库连接
事务执行在数据库连接层上,所以需要在事务过程中维护一个数据库连接
ADO.Net分布事务也可以跨越多个数据库,但是其中一个SQL SERVER 数据库的话,通过用SQL SERVER连接服务器连接到别的数据库,但是如果是在DB2和Orcal之间就不可以。
以上两种事务是经常用到的事务处理方法。
方法3 COM+事务(分布式事务)
一般的数据库事务控制要求事务里所有的操作必须在同一个数据库内,这样在出现错误的时候才能回滚(RllBack)到初始状态。这就存在一个问题,在分布式应用程序中,我们往往需要同时操作多个数据库,使用数据库本身的事务处理,很难满足程序对事务控制的要求。在COM+中,提供了完整的事务服务,我们可以利用它来完成在分布式应用程序中的事务控制。
.Net Framework 依靠 MTS/COM+ 服务来支持自动事务。COM+ 使用 Microsoft Distributed Transaction Coordinator (DTC) 作为事务管理器和事务协调器在分布式环境中运行事务。
这样可使 .Net 应用程序运行跨多个资源结合不同操作(例如,将定单插入 SQL Server 数据库、将消息写入 Microsoft 消息队列 (MSMQ) 队列、以及从 Oracle 数据库检索数据)
的事务。
COM+事务处理的类必须继承System.EnterpriseServices.ServicedComponent,其实web service就是继承System.EnterpriseServices.ServicedComponent,所以web service也支持COM+事务。
创建参与自动事务的类
1. 将TransactionAttribute类应用于您的类,指定组件请求的自动事务类型。事务类型必须是TransactionOption枚举的成员。
2. 从ServicedComponent类派生您的类。SercivedComponent是所有使用COM+服务的类的基类。
3. 用强名称(stiong name)标记(sign)程序集(阿瑟sembly),确保程序集包含唯一的密钥对。
4. 在COM+catalog中注册包含您的类的程序集
具体过程如下
一:用VS.NET生成一个类库 。
二:添加对System.EnterpristServices的引用,具体步骤
菜单:(项目-添加引用-在.NET选项卡选择System.EnterpristServices-确定)
三:构建类
1:源程序
一般来说COM+中的组件需要Required 或Supported。当组件用于记录或查帐时RequiresNew 很有用,因为组件应该与活动中其他事务处理的提交或回滚隔离开来。
派生类可以重载基类的任意属性。如DataAccess选用Required,派生类仍然可以重载并指定RequiresNew或其他值。
COM+事务有手动处理和自动处理,自动处理就是在所需要自动处理的方法前加上[AutoComplete],根据方法的正常或抛出异常决定提交或回滚。
手动处理就是调用ContextUtil类中EnableCommit,SetComplete,SetAbort方法。
public string testTransaction()
{
try
{
ContextUtil.EnableCommit();
InsertARecord1();
InsertARecord2();
ContextUtil.SetComplete();
return "succeed!";
}
catch(Exception ex)
{
ContextUtil.SetAbort();
return "failed!";
}
}
public void InsertARecord1()
{
string strconn="workstation id=WEIXIAOPING;packet size=4096;user id=sa;initial catalog=Northwind;persist security info=False";
SqlConnection conn=new SqlConnection(strconn);
conn.Open();
SqlCommand command=new SqlCommand("insert into tbTree(Context,ParentID) values('北京',1)",conn);
command.ExecuteNonQuery();
conn.Close();
}
public void InsertARecord2()
{
string strconn="workstation id=WEIXIAOPING;packet size=4096;user id=sa;initial catalog=Northwind;persist security info=False";
SqlConnection conn=new SqlConnection(strconn);
conn.Open();
SqlCommand command=new SqlCommand("insert into tbTree(Context,ParentID) values('上海',1)",conn);
command.ExecuteNonQuery();
conn.Close();
}
2:程序说明:
添加命名空间 using System.EnterpriseServices;因为本程序使用了其中的ContextUtil类
[ Transaction(TransactionOption.Required) ] 说明DLL需要事务支持
TransactionOption 枚举类型支持5个COM+值
Disabled 忽略当前上下文中的任何事务。
NotSupported 使用非受控事务在上下文中创建组件。
Required 如果事务存在则共享事务,并且如有必要则创建新事务。
RequiredNew 使用新事务创建组建,而与当前上下文的状态无关。
Supported 如果事务存在,则共享该事务
本程序的TxCfgClass 类从ServicedComponent类中继承,这样并不会影响该类,而只是在该类中添加了两个额外的方法,这两个方法可以使代码共享变得更加容易
程序使用的sql server数据库在本机运行,init1 和 init2是两个连接数据库的连接字符串,init连接pubs数据库,inin2连接northwind数据库,这是sql2000中自带的示例数据库。add1和add2是两条sql语句,作用是分别向两个数据库的表里添加一条记录。注意:add2是一条错误的语句,因为根本没有sample表,这样,会在执行时引起异常。(这正是我们所期望的)
在执行到add2语句时,由于它是错误的,所以会引发异常,转到错误处理语句里来执行。
ContextUtil.SetAbort();该语句使所有的数据库操作回滚,这样add1语句所插入的记录也将不存在。(达到预期目标)
四:给程序添加强名(strong name)
为使该组件正确运行,该组件必须有一个强名称。生成一个强名称,然后使用该强名称对程序集进行签名
1:创建一对密钥
用来创建密钥的工具是称为sn.exe的共享工具。通常通过命令提示运行它,该工具可执行各种任务以生成并提取密钥。我们需要用以下方式来运行sn.exe。
sn -k keyPair.snk
其中key.snk 代表将保存密钥的文件的名称。它的名称可以是任意的,不过习惯上带有.snk后缀名。
2:签名
签名通常是在编译时进行的。签名时,用户可利用C#属性通知编译器应该使用正确的密钥文件对DLL进行签名。要做到这一点用户需要打开工程中的AssemblyInfo.cs文件并进行修改。
[assembly:AssemblyKeyFile(“..\\..\\key.snk”)]
注:keyPair.snk文件和项目文件在同一个文件夹
五:编译成DLL (具体步骤)
菜单:(生成-生成)
如果一切正常,就会生成DLL文件
进行调试时,事务在提交或终止前可能会超时。要避免出现超时,请在事务属性中使用一个超时属性。在下面的示例中,在完成任何事务时,关联的方 法,在超时前都有1,200秒的执行时间。[Transaction(TransactionOption.Requried,timeout=1022)]
六:使用regsvcs.exe将Dll注册到COM+ Services里面
我们需要用以下方式运行regsvcs.exe
regsvcs dll文件名
如果一切正常的话,regsvcs.exe就会把dll输入到COM+ Services中。
或者直接在COM+ Service中进行创建添加。(右击“COM+应用程序”→“新建”→“应用程序“,进入COM+应用程序安装向导,”下一步“→”安装空应用程序“→”输入新应用程序的名称,选择库应用程序“→”下一步“→”完成“,右击新建的库应用程序下的”组建“→”新建“→”组件“,进入组件安装向导,选择添加需要添加的dll文件,安装即可)
至此,我们已经生成并注册了这个可以由其它程序使用的类,现在,我们来写一个控制台程序来检验这个类是否正常运行
七:构建客户机
1:新建控制台应用程序项目
菜单(文件-新建-项目)
选择控制台应用程序 ,并选择 添入解决方案 ,确定
2:同上面的第二步一样,添加对System.EnterpriseServices的引用。
3:添加对自己刚才做好的类的引用。
菜单(项目-添加引用-浏览),选择刚才生成的DLL,确定
4:输入以下程序
using System;
using COMPlusSamples;
using System.EnterpriseServices;
public class Client
{
public static void Main()
{
TxCfgClass cfg = new TxCfgClass();
cfg.Add();
}
}
5:将控制台程序设置为启动项,然后编译运行,就会看到结果。
正如我们希望的,第一条记录没有插入数据库
在需要事务跨 MSMQ 和其他可识别事务的资源(例如,SQL Server 数据库)运行的系统中,只能使用 DTC 或 COM+ 事务,除此之外没有其他选择。DTC 协调参与分布式事务的所有资源管理器,
也管理与事务相关的操作。
这种做法的缺点是,由于存在 DTC 和 COM 互操作性开销,导致性能降低。
COM+事务处理的类必须强命名.
什么是异常处理
异常是正在执行的程序遇到的任何错误情形或者以外行为。
很多原因都是可以引起异常,例如,代码中错误、操作系统资源不可用、公共语言运行时(conmmon language runtime)中的以外情况等等
然而应用程序能够从上述的一些情况中恢复执行,但是大多数运行时异常不可恢复的。在这种情况下,需要一种有效的方法来处理这些异常并给调用者提供相同的异常。
用结构化的异常处理方法来处理异常
在.net web服务中,对异常处理支持的关键点是由 try……catch……finally语句提供的。
关键字try放在可能抛出异常的普通处理代码块之前。
关键字catch放在异常处理代码块之前。
关键字finally放在那些经常在异常处理后还需要执行的代码块之前。
一旦异常从try代码块中抛出,程序流切换到后面的第一个catch代码块。
异常类
Exception所有异常对象的基类
SystemException运行时产生的所有错误的基类
IndexOutOfRangeException当一个数组的下标超出范围是运行时引发
NullRefetenceException当一个空对象被引用是运行时引发
InvaliOperationException当对方法的调用对对象的当前状态无效时,由某些方法引用。
ArgumentException所有参数异常的基类
ArgumentNullException在参数为空(不允许)的情况下,由方法引用。
ArgumentOutOfRangeException当参数不在一个给定范围之内时,由方法引发。
IneropException目标在或发生在CLR外面环境中的异常的基类
ComException包含COM类的HRESULT信息的异常
SEHException封装win32结构异常处理信息的异常
DEMO4 结构化异常处理
优化异常
理解异常是一定会发生的
大多数的软件系统都不是百分之百可靠的
要站在异常一定可能会发生的角度来编写异常处理程序,应对程序有可能发生的错误。
建立一个良好的异常处理策略。
处理未预料的异常
确保所有程序的入口都使用了try-catch
在catch中截获所有的异常。
异常处理注意事项
当引发异常时,要提供有意义的文本
要引发异常仅当条件是真正异常;也就是当一个正常的返回值不满足时。
如果你的方法或属性被传递一个坏参数,要引发一个ArgumentException异常
当调用操作不适合对象的当前状态时,要引发一个InvalidOoerationException异常。
要引发最合适的异常
要是链接异常,他们允许你跟踪异常树。
不要为正常或预期的错误使用异常
不要为流程的正常控制使用异常
不要在方法中引发NullReferenceException或IndexOutOfRangeException异常
异常处理技术
记录异常
在文件中记录异常
在数据库中记录异常
在eventlog中记录异常
发送email通知异常
异常产生时,用友好(user-发riendly)的方式通知用户
处理错误
Page_Error事件
Application_Error事件
利用配置文件,自定义错误页面
<customErrors defaultRedirect=”url” mode”RemoteOnly”>
<error statusCode=”code” redirect=”url”></errpr>
</customErrors>
DEMO5编写到windows错误日志把错误发送邮件到管理员自定义错误页面
protected void Application_Error(Object sender, EventArgs e)
{
//把错误信息发送到作者
string strPageUrl = Request.Path;
Exception ErrorInfo =Server.GetLastError();
//Server.ClearError();
string strMessage = "Url:" + strPageUrl + "</br>";
strMessage = strMessage + " Error: ";
strMessage = strMessage + ErrorInfo.ToString() + "</br>";
MailMessage Mymessage = new MailMessage();
Mymessage.To = "wkjs@163.com";
Mymessage.From = "wkjs@163.com";
Mymessage.Subject = "ASP.NET Error";
Mymessage.BodyFormat = MailFormat.Text;
Mymessage.Body = strMessage;
SmtpMail.Send(Mymessage);
}
private void btnCal_Click(object sender, System.EventArgs e)
{
try
{
int nInput = int.Parse(tbInput.Text);
long nResult=1;
for(int i=1;i<=nInput;i++)
checked{nResult *= i;}
lbResult.Text = nResult.ToString();
}
catch(OverflowException)//溢出错误异常处理
{
throw new Exception("输入的数字太大,我受不了了!");
}
catch(FormatException)//输入字符格式错误异常处理
{
Response.Write("嘿!要输入整数!");
}
catch(Exception ee)
{
Response.Write("发生错误,信息为:"+ee.Message);
}
}
private void WebForm1_Error(object sender, System.EventArgs e)
{
string strMessage = Server.GetLastError().Message;
//Response.Write(strMessage);
Server.ClearError();
//以下把信息写入windows日志
//要把aspnet用户添加到管理员组中,以便有写注册表权限
if(!EventLog.SourceExists("mySource"))
EventLog.CreateEventSource("mySource","myLog");
EventLog Event = new EventLog();
Event.Source = "mySource";
Event.WriteEntry(strMessage,EventLogEntryType.Warning);
//EventLog.Delete("myLog");
throw new Exception("我处理不了,请最高人民法院处理!");
}