在一个MIS系统中,没有用事务那就绝对是有问题的,要么就只有一种情况:你的系统实在是太小了,业务业务逻辑有只要一步执行就可以完成了。因此掌握事务处理的方法是很重要,进我的归类在.net中大致有以下4种事务处理的方法。大家可以参考一下,根据实际选择适当的事务处理。
1、SQL事务
sql事务是使用SQL server自身的事务:在存储过程中直接使用Begin Tran,Rollback Tran,Commit Tran实现事务:
优点:执行效率最佳
限制:事务上下文仅在数据库中调用,难以实现复杂的业务逻辑。
Demo:(所有demo,都以SQL Server自带的Northwind数据的表Region为例)
带事务的存储过程
CREATE PROCEDURE dbo.SPTransaction
(
@UpdateID int,
@UpdateValue nchar( 50),
@InsertID int,
@InsertValue nchar( 50)
)
AS
begin Tran
Update Region Set where RegionID=@UpdateID
insert into Region Values (@InsertID,@InsertValue)
declare @RegionError int
select @RegionError=@@error
if(@RegionError= 0)
COMMIT Tran
else
ROLLBACK Tran
GO
执行带事务的存储过程
/**/ /// <summary>
/// SQL事务:
/// </summary>
public void SQLTran()
{
SqlConnection conn = new SqlConnection( " Data Source=127.0.0.1;Initial Catalog=Northwind;Persist Security Info=True;User );
SqlCommand cmd = new SqlCommand();
cmd.CommandText = " SPTransaction ";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = conn;
conn.Open();
SqlParameter[] paras= new SqlParameter[]{
new SqlParameter ( " @UpdateID ",SqlDbType.Int, 32),
new SqlParameter ( " @UpdateValue ",SqlDbType .NChar, 50),
new SqlParameter ( " @InsertID ",SqlDbType.Int , 32),
new SqlParameter ( " @InsertValue ",SqlDbType.NChar , 50)};
paras[ 0].Value = " 2 ";
paras[ 1].Value = " Update Value1 ";
paras[ 2].Value = " 6 ";
paras[ 3].Value = " Insert Value1 ";
foreach (SqlParameter para in paras )
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
}
2、ADO.net事务
Ado.net事务可能是大家一般都用的
优点:简单,效率和数据库事务差不多。
缺点:事务不能跨数据库,只能在一个数据库连接上。如果是两个数据库上就不能使用该事务了。
Demo:
ADO.net事务
/**/ /// <summary>
/// 一般的ADO.net 事务
/// </summary>
public void ADONetTran1()
{
SqlConnection conn = new SqlConnection( " Data Source=127.0.0.1;Initial Catalog=Northwind;Persist Security Info=True;User );
SqlCommand cmd = new SqlCommand();
try
{
cmd.CommandText = " Update Region Set where RegionID=@UpdateID ";
cmd.CommandType = CommandType.Text;
cmd.Connection = conn;
conn.Open();
SqlParameter[] paras = new SqlParameter[]{
new SqlParameter ( " @UpdateID ",SqlDbType.Int, 32),
new SqlParameter ( " @UpdateValue ",SqlDbType .NChar, 50)};
paras[ 0].Value = " 2 ";
paras[ 1].Value = " Update Value12 ";
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
// 开始事务
cmd.Transaction = conn.BeginTransaction();
cmd.ExecuteNonQuery();
cmd.CommandText = " insert into Region values(@InsertID,@InsertValue) ";
cmd.CommandType = CommandType.Text;
paras = new SqlParameter[]{
new SqlParameter ( " @InsertID ",SqlDbType.Int , 32),
new SqlParameter ( " @InsertValue ",SqlDbType.NChar , 50)};
paras[ 0].Value = " 7 ";
paras[ 1].Value = " Insert Value ";
cmd.Parameters.Clear();
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
// 提交事务
cmd.Transaction.Commit();
}
catch
{
// 回滚事务
cmd.Transaction.Rollback();
throw;
}
finally
{
conn.Close();
}
}
3、TransactionScope事务
TransactionScope事务类,它可以使代码块成为事务性代码。并自动提升为分布式事务
优点:实现简单,同时能够自动提升为分布式事务
Demo:
TransactionScope事务
/**/ /// <summary>
/// TransactionScope事务:可自动提升事务为完全分布式事务的轻型(本地)事务。
/// 使用时要保证MSDTC服务(控制分布事务)是开启的可以使用:net start msdtc命令开启服务;
/// </summary>
public void ADONetTran2()
{
SqlConnection conn = new SqlConnection( " Data Source=127.0.0.1;Initial Catalog=Northwind;Persist Security Info=True;User );
SqlCommand cmd = new SqlCommand();
try
{
using (System.Transactions.TransactionScope ts = new TransactionScope())
{
cmd.CommandText = " Update Region Set where RegionID=@UpdateID ";
cmd.CommandType = CommandType.Text;
cmd.Connection = conn;
conn.Open();
SqlParameter[] paras = new SqlParameter[]{
new SqlParameter ( " @UpdateID ",SqlDbType.Int, 32),
new SqlParameter ( " @UpdateValue ",SqlDbType .NChar, 50)};
paras[ 0].Value = " 2 ";
paras[ 1].Value = " Update Value12 ";
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
cmd.CommandText = " insert into Region values(@InsertID,@InsertValue) ";
cmd.CommandType = CommandType.Text;
paras = new SqlParameter[]{
new SqlParameter ( " @InsertID ",SqlDbType.Int , 32),
new SqlParameter ( " @InsertValue ",SqlDbType.NChar , 50)};
paras[ 0].Value = " 8 ";
paras[ 1].Value = " Insert Value ";
cmd.Parameters.Clear();
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
// 提交事务
ts.Complete(); // 提交事务之前conn不能关闭的(Not conn.Close()),否则报错
}
}
catch
{
throw;
}
finally
{
conn.Close();
}
}
4、COM+事务
在分布式应用程序中,往往要同时操作多个数据库,使用数据库事务就不能满足业务的要求了。在COM+中,提供完整的事务处理服务。很方便处理多个数据库上的事务。
例一:
COM+事务
/**/ /// <summary>
/// COM+事务
/// </summary>
public void ComTran()
{
SqlConnection conn = new SqlConnection( " Data Source=127.0.0.1;Initial Catalog=Northwind;Persist Security Info=True;User );
SqlCommand cmd = new SqlCommand();
ServiceConfig sc = new ServiceConfig();
// 指定事务类型
sc.Transaction = TransactionOption.Required;
// 设置启动跟踪
sc.TrackingEnabled = true;
// 创建一个上下文,该上下文的配置由作为 cfg 参数传递的 ServiceConfig 对象来指定。
// 随后,客户端和服务器端的策略均被触发,如同发生了一个方法调用。
// 接着,新的上下文被推至上下文堆栈,成为当前上下文
ServiceDomain.Enter(sc);
try
{
cmd.CommandText = " Update Region Set where RegionID=@UpdateID ";
cmd.CommandType = CommandType.Text;
cmd.Connection = conn;
conn.Open();
SqlParameter[] paras = new SqlParameter[]{
new SqlParameter ( " @UpdateID ",SqlDbType.Int, 32),
new SqlParameter ( " @UpdateValue ",SqlDbType .NChar, 50)};
paras[ 0].Value = " 2 ";
paras[ 1].Value = " Update Value22 ";
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
cmd.CommandText = " insert into Region values(@InsertID,@InsertValue) ";
cmd.CommandType = CommandType.Text;
paras = new SqlParameter[]{
new SqlParameter ( " @InsertID ",SqlDbType.Int , 32),
new SqlParameter ( " @InsertValue ",SqlDbType.NChar , 50)};
paras[ 0].Value = " 9 ";
paras[ 1].Value = " Insert Value ";
cmd.Parameters.Clear();
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
// 提交事务
ContextUtil.SetComplete();
}
catch
{
// 回滚事务
ContextUtil.SetAbort();
throw;
}
finally
{
conn.Close();
// 触发服务器端的策略,随后触发客户端的策略,如同一个方法调用正在返回。
// 然后,当前上下文被弹出上下文堆栈,调用 Enter 时正在运行的上下文成为当前的上下文。
ServiceDomain.Leave();
}
需要特别补充的是:
如果你使用的是分布事务(TransactionScope事务和COM+事务),在默认情况下你是要重新配置安装SQL Server数据库服务器和访问数据库的客户端的.(如果没有配置运行会出现以下错误:该伙伴事务管理器已经禁止了它对远程/网络事务的支持。 (异常来自 HRESULT: 0x8004D025)
)下面是MSDN上关于配置分布式事务的一段原话:
配置分布式事务
要启用分布式事务,可能需要通过网络启用 MS DTC,以便在使用应用了最新的 Service Pack 的较新操作系统(例如 Windows XP 或 Windows 2003)时使用分布式事务。如果启用了 Windows 防火墙(Windows XP Service Pack 2 的默认设置),必须允许 MS DTC 服务使用网络或打开 MS DTC 端口。
实际怎么配置呢,经过我的实际使用:大致如下:打开 ' 控制面板 '-> ' 管理工具 '-> ' 组件服务 ',点开 ' 组件服务 '-> ' 计算机 '-> ' 我的电脑 ',在 ' 我的电脑 '上右击属性,点 ' MSDTC ',然后点 ' 安全性配置 '。作为数据库的服务器的配置如下:
而访问数据库的客户端的配置和服务器端的稍有些差别:
在设置完上面的还有使防火墙MS DTC 服务使用网络或打开 MS DTC 端口:运行netsh firewall set allowedprogram %windir%\system32\msdtc.exe MSDTC enable命令就可以了
ASP.NET事务可以说是在.NET平台上事务实现方式最简单的一种,你仅仅需要一行代码即可。在aspx的页面声明中加一个额外的属性,即事务属性Transaction= " Required ",它有如下的值:Disabled(默认)、NotSupported、Supported、Required和RequiresNew,这些设置和COM+及企业级服务中的设置一样,典型的一个例子是如果你想在页面上下文中运行事务,那么要将其设置为Required。如果页面中包含有用户控件,那么这些控件也会包含到事务中,事务会存在于页面的每个地方。
页面声明Transaction= " Required ":
<%@ Page Transaction= " Required " Language= " C# " AutoEventWireup= " true "
CodeBehind= " WebForm3.aspx.cs " Inherits= " WebApplication4.WebForm3 " %>
页面引用: using System.EnterpriseServices;。
然后,数据操作代码:
protected void Button1_Click( object sender, EventArgs e)
{
try
{
Work1();
Work2();
ContextUtil.SetComplete(); // 提交事务
}
catch (System.Exception except)
ContextUtil.SetAbort(); // 撤销事务
Response.Write(except.Message);
}
}
private void Work1()
{
string conString = " data source=127.0.0.1;database=codematic;user >
password= " ;
SqlConnection myConnection = new SqlConnection(conString);
string strSql = " Insert Into P_Category(CategoryId,Name)values('1',
' test1 ') " ;
SqlCommand myCommand = new SqlCommand(strSql, myConnection);
myConnection.Open();
int rows = myCommand.ExecuteNonQuery();
myConnection.Close();
}
private void Work2()
{
string conString = " data source=127.0.0.1;database=codematic;user >
password= " ;
SqlConnection myConnection = new SqlConnection(conString);
string strSql = " Insert Into P_Category(CategoryId,Name)values('2',
' test2 ') " ;
SqlCommand myCommand = new SqlCommand(strSql, myConnection);
myConnection.Open();
int rows = myCommand.ExecuteNonQuery();
myConnection.Close();
}
ContextUtil是用于获取 COM+ 上下文信息的首选类。由于此类的成员全部为static,因此在使用其成员之前不需要对此类进行实例化。
ASP.NET页面事务的优势和限制如下。
l限制:页面的所有代码都是同一个事务,这样的事务可能会很大,而也许我们需要的是分开的、小的事务实现在Web层。
5、ASP.net web 服务事务
略……
1、SQL事务
sql事务是使用SQL server自身的事务:在存储过程中直接使用Begin Tran,Rollback Tran,Commit Tran实现事务:
优点:执行效率最佳
限制:事务上下文仅在数据库中调用,难以实现复杂的业务逻辑。
Demo:(所有demo,都以SQL Server自带的Northwind数据的表Region为例)
带事务的存储过程
CREATE PROCEDURE dbo.SPTransaction
(
@UpdateID int,
@UpdateValue nchar( 50),
@InsertID int,
@InsertValue nchar( 50)
)
AS
begin Tran
Update Region Set where RegionID=@UpdateID
insert into Region Values (@InsertID,@InsertValue)
declare @RegionError int
select @RegionError=@@error
if(@RegionError= 0)
COMMIT Tran
else
ROLLBACK Tran
GO
执行带事务的存储过程
/**/ /// <summary>
/// SQL事务:
/// </summary>
public void SQLTran()
{
SqlConnection conn = new SqlConnection( " Data Source=127.0.0.1;Initial Catalog=Northwind;Persist Security Info=True;User );
SqlCommand cmd = new SqlCommand();
cmd.CommandText = " SPTransaction ";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = conn;
conn.Open();
SqlParameter[] paras= new SqlParameter[]{
new SqlParameter ( " @UpdateID ",SqlDbType.Int, 32),
new SqlParameter ( " @UpdateValue ",SqlDbType .NChar, 50),
new SqlParameter ( " @InsertID ",SqlDbType.Int , 32),
new SqlParameter ( " @InsertValue ",SqlDbType.NChar , 50)};
paras[ 0].Value = " 2 ";
paras[ 1].Value = " Update Value1 ";
paras[ 2].Value = " 6 ";
paras[ 3].Value = " Insert Value1 ";
foreach (SqlParameter para in paras )
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
}
2、ADO.net事务
Ado.net事务可能是大家一般都用的
优点:简单,效率和数据库事务差不多。
缺点:事务不能跨数据库,只能在一个数据库连接上。如果是两个数据库上就不能使用该事务了。
Demo:
ADO.net事务
/**/ /// <summary>
/// 一般的ADO.net 事务
/// </summary>
public void ADONetTran1()
{
SqlConnection conn = new SqlConnection( " Data Source=127.0.0.1;Initial Catalog=Northwind;Persist Security Info=True;User );
SqlCommand cmd = new SqlCommand();
try
{
cmd.CommandText = " Update Region Set where RegionID=@UpdateID ";
cmd.CommandType = CommandType.Text;
cmd.Connection = conn;
conn.Open();
SqlParameter[] paras = new SqlParameter[]{
new SqlParameter ( " @UpdateID ",SqlDbType.Int, 32),
new SqlParameter ( " @UpdateValue ",SqlDbType .NChar, 50)};
paras[ 0].Value = " 2 ";
paras[ 1].Value = " Update Value12 ";
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
// 开始事务
cmd.Transaction = conn.BeginTransaction();
cmd.ExecuteNonQuery();
cmd.CommandText = " insert into Region values(@InsertID,@InsertValue) ";
cmd.CommandType = CommandType.Text;
paras = new SqlParameter[]{
new SqlParameter ( " @InsertID ",SqlDbType.Int , 32),
new SqlParameter ( " @InsertValue ",SqlDbType.NChar , 50)};
paras[ 0].Value = " 7 ";
paras[ 1].Value = " Insert Value ";
cmd.Parameters.Clear();
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
// 提交事务
cmd.Transaction.Commit();
}
catch
{
// 回滚事务
cmd.Transaction.Rollback();
throw;
}
finally
{
conn.Close();
}
}
3、TransactionScope事务
TransactionScope事务类,它可以使代码块成为事务性代码。并自动提升为分布式事务
优点:实现简单,同时能够自动提升为分布式事务
Demo:
TransactionScope事务
/**/ /// <summary>
/// TransactionScope事务:可自动提升事务为完全分布式事务的轻型(本地)事务。
/// 使用时要保证MSDTC服务(控制分布事务)是开启的可以使用:net start msdtc命令开启服务;
/// </summary>
public void ADONetTran2()
{
SqlConnection conn = new SqlConnection( " Data Source=127.0.0.1;Initial Catalog=Northwind;Persist Security Info=True;User );
SqlCommand cmd = new SqlCommand();
try
{
using (System.Transactions.TransactionScope ts = new TransactionScope())
{
cmd.CommandText = " Update Region Set where RegionID=@UpdateID ";
cmd.CommandType = CommandType.Text;
cmd.Connection = conn;
conn.Open();
SqlParameter[] paras = new SqlParameter[]{
new SqlParameter ( " @UpdateID ",SqlDbType.Int, 32),
new SqlParameter ( " @UpdateValue ",SqlDbType .NChar, 50)};
paras[ 0].Value = " 2 ";
paras[ 1].Value = " Update Value12 ";
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
cmd.CommandText = " insert into Region values(@InsertID,@InsertValue) ";
cmd.CommandType = CommandType.Text;
paras = new SqlParameter[]{
new SqlParameter ( " @InsertID ",SqlDbType.Int , 32),
new SqlParameter ( " @InsertValue ",SqlDbType.NChar , 50)};
paras[ 0].Value = " 8 ";
paras[ 1].Value = " Insert Value ";
cmd.Parameters.Clear();
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
// 提交事务
ts.Complete(); // 提交事务之前conn不能关闭的(Not conn.Close()),否则报错
}
}
catch
{
throw;
}
finally
{
conn.Close();
}
}
4、COM+事务
在分布式应用程序中,往往要同时操作多个数据库,使用数据库事务就不能满足业务的要求了。在COM+中,提供完整的事务处理服务。很方便处理多个数据库上的事务。
例一:
COM+事务
/**/ /// <summary>
/// COM+事务
/// </summary>
public void ComTran()
{
SqlConnection conn = new SqlConnection( " Data Source=127.0.0.1;Initial Catalog=Northwind;Persist Security Info=True;User );
SqlCommand cmd = new SqlCommand();
ServiceConfig sc = new ServiceConfig();
// 指定事务类型
sc.Transaction = TransactionOption.Required;
// 设置启动跟踪
sc.TrackingEnabled = true;
// 创建一个上下文,该上下文的配置由作为 cfg 参数传递的 ServiceConfig 对象来指定。
// 随后,客户端和服务器端的策略均被触发,如同发生了一个方法调用。
// 接着,新的上下文被推至上下文堆栈,成为当前上下文
ServiceDomain.Enter(sc);
try
{
cmd.CommandText = " Update Region Set where RegionID=@UpdateID ";
cmd.CommandType = CommandType.Text;
cmd.Connection = conn;
conn.Open();
SqlParameter[] paras = new SqlParameter[]{
new SqlParameter ( " @UpdateID ",SqlDbType.Int, 32),
new SqlParameter ( " @UpdateValue ",SqlDbType .NChar, 50)};
paras[ 0].Value = " 2 ";
paras[ 1].Value = " Update Value22 ";
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
cmd.CommandText = " insert into Region values(@InsertID,@InsertValue) ";
cmd.CommandType = CommandType.Text;
paras = new SqlParameter[]{
new SqlParameter ( " @InsertID ",SqlDbType.Int , 32),
new SqlParameter ( " @InsertValue ",SqlDbType.NChar , 50)};
paras[ 0].Value = " 9 ";
paras[ 1].Value = " Insert Value ";
cmd.Parameters.Clear();
foreach (SqlParameter para in paras)
{
cmd.Parameters.Add(para);
}
cmd.ExecuteNonQuery();
// 提交事务
ContextUtil.SetComplete();
}
catch
{
// 回滚事务
ContextUtil.SetAbort();
throw;
}
finally
{
conn.Close();
// 触发服务器端的策略,随后触发客户端的策略,如同一个方法调用正在返回。
// 然后,当前上下文被弹出上下文堆栈,调用 Enter 时正在运行的上下文成为当前的上下文。
ServiceDomain.Leave();
}
需要特别补充的是:
如果你使用的是分布事务(TransactionScope事务和COM+事务),在默认情况下你是要重新配置安装SQL Server数据库服务器和访问数据库的客户端的.(如果没有配置运行会出现以下错误:该伙伴事务管理器已经禁止了它对远程/网络事务的支持。 (异常来自 HRESULT: 0x8004D025)
)下面是MSDN上关于配置分布式事务的一段原话:
配置分布式事务
要启用分布式事务,可能需要通过网络启用 MS DTC,以便在使用应用了最新的 Service Pack 的较新操作系统(例如 Windows XP 或 Windows 2003)时使用分布式事务。如果启用了 Windows 防火墙(Windows XP Service Pack 2 的默认设置),必须允许 MS DTC 服务使用网络或打开 MS DTC 端口。
实际怎么配置呢,经过我的实际使用:大致如下:打开 ' 控制面板 '-> ' 管理工具 '-> ' 组件服务 ',点开 ' 组件服务 '-> ' 计算机 '-> ' 我的电脑 ',在 ' 我的电脑 '上右击属性,点 ' MSDTC ',然后点 ' 安全性配置 '。作为数据库的服务器的配置如下:
而访问数据库的客户端的配置和服务器端的稍有些差别:
在设置完上面的还有使防火墙MS DTC 服务使用网络或打开 MS DTC 端口:运行netsh firewall set allowedprogram %windir%\system32\msdtc.exe MSDTC enable命令就可以了
ASP.NET事务可以说是在.NET平台上事务实现方式最简单的一种,你仅仅需要一行代码即可。在aspx的页面声明中加一个额外的属性,即事务属性Transaction= " Required ",它有如下的值:Disabled(默认)、NotSupported、Supported、Required和RequiresNew,这些设置和COM+及企业级服务中的设置一样,典型的一个例子是如果你想在页面上下文中运行事务,那么要将其设置为Required。如果页面中包含有用户控件,那么这些控件也会包含到事务中,事务会存在于页面的每个地方。
页面声明Transaction= " Required ":
<%@ Page Transaction= " Required " Language= " C# " AutoEventWireup= " true "
CodeBehind= " WebForm3.aspx.cs " Inherits= " WebApplication4.WebForm3 " %>
页面引用: using System.EnterpriseServices;。
然后,数据操作代码:
protected void Button1_Click( object sender, EventArgs e)
{
try
{
Work1();
Work2();
ContextUtil.SetComplete(); // 提交事务
}
catch (System.Exception except)
ContextUtil.SetAbort(); // 撤销事务
Response.Write(except.Message);
}
}
private void Work1()
{
string conString = " data source=127.0.0.1;database=codematic;user >
password= " ;
SqlConnection myConnection = new SqlConnection(conString);
string strSql = " Insert Into P_Category(CategoryId,Name)values('1',
' test1 ') " ;
SqlCommand myCommand = new SqlCommand(strSql, myConnection);
myConnection.Open();
int rows = myCommand.ExecuteNonQuery();
myConnection.Close();
}
private void Work2()
{
string conString = " data source=127.0.0.1;database=codematic;user >
password= " ;
SqlConnection myConnection = new SqlConnection(conString);
string strSql = " Insert Into P_Category(CategoryId,Name)values('2',
' test2 ') " ;
SqlCommand myCommand = new SqlCommand(strSql, myConnection);
myConnection.Open();
int rows = myCommand.ExecuteNonQuery();
myConnection.Close();
}
ContextUtil是用于获取 COM+ 上下文信息的首选类。由于此类的成员全部为static,因此在使用其成员之前不需要对此类进行实例化。
ASP.NET页面事务的优势和限制如下。
l限制:页面的所有代码都是同一个事务,这样的事务可能会很大,而也许我们需要的是分开的、小的事务实现在Web层。
5、ASP.net web 服务事务
略……