使用 TransactionScope 时避免启用 MSDTC
[使用:C# 3.5 + SQL Server 2005]
我在业务层中有一些代码,它们将订单的创建及其详细信息包装在 TransactionScope 中:
DAL.DAL_OrdenDeCompra dalOrdenDeCompra = new GOA.DAL.DAL_OrdenDeCompra();
DAL.DAL_ItemDeUnaOrden dalItemDeUnaOrden = new GOA.DAL.DAL_ItemDeUnaOrden();
using (TransactionScope transaccion = new TransactionScope())
{
//Insertion of the order
orden.Id = dalOrdenDeCompra.InsertarOrdenDeCompra(orden.NumeroOrden, orden.PuntoDeEntregaParaLaOrden.Id, (int)orden.TipoDeCompra, orden.FechaOrden, orden.Observaciones);
foreach (ItemDeUnaOrden item in orden.Items)
{
//Insertion of each one of its items.
dalItemDeUnaOrden.InsertarItemDeUnaOrden(orden.Id, item.CodigoProductoAudifarma, item.CodigoProductoJanssen, item.CodigoEAN13, item.Descripcion, item.CantidadOriginal, item.ValorUnitario);
}
transaccion.Complete();
}
return true;
这是执行插入的 DAL 代码:
public int InsertarOrdenDeCompra(string pNumeroOrden, int pPuntoEntregaId, int pTipoDeCompra, DateTime pFechaOrden, string pObservaciones)
{
try
{
DataTable dataTable = new DataTable();
using (SqlConnection conexion = new SqlConnection())
{
using (SqlCommand comando = new SqlCommand())
{
ConnectionStringSettings conString = ConfigurationManager.ConnectionStrings["CSMARTDB"];
conexion.ConnectionString = conString.ConnectionString;
conexion.Open();
comando.Connection = conexion;
comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = "GOA_InsertarOrdenDeCompra";
//...parameters setting
return (int)comando.ExecuteScalar();
...
public int InsertarItemDeUnaOrden(int pOrdenDeCompraId, string pCodigoProductoAudifarma, string pCodigoProductoJanssen, string pCodigoEAN13, string pDescripcion, int pCantidadOriginal, decimal pValorUnitario)
{
try
{
DataTable dataTable = new DataTable();
using (SqlConnection conexion = new SqlConnection())
{
using (SqlCommand comando = new SqlCommand())
{
ConnectionStringSettings conString = ConfigurationManager.ConnectionStrings["CSMARTDB"];
conexion.ConnectionString = conString.ConnectionString;
conexion.Open();
comando.Connection = conexion;
comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = "GOA_InsertarItemDeUnaOrden";
//... parameters setting
return comando.ExecuteNonQuery();
现在,我的问题在于项目插入;当 InsertarItemDeUnaOrden 尝试打开新连接时,会出现异常,因为这会导致 TransactionScope 尝试升级到 MSDTC,而我没有启用它,而且我不想启用。
我的疑问:
- 了解启动事务的方法是在业务层中,并且我不希望有任何 SqlConnection,我可以使用另一种设计来进行数据访问,以便我能够重用相同的连接吗?
- 我应该启用 MSDTC 并忘记它吗?
谢谢。
编辑:解决方案
我在 DAL 中创建了一个新类来保存如下事务:
namespace GOA.DAL
{
public class DAL_Management
{
public SqlConnection ConexionTransaccional { get; set; }
public bool TransaccionAbierta { get; set; }
public DAL_Management(bool pIniciarTransaccion)
{
if (pIniciarTransaccion)
{
this.IniciarTransaccion();
}
else
{
TransaccionAbierta = false;
}
}
private void IniciarTransaccion()
{
this.TransaccionAbierta = true;
this.ConexionTransaccional = new SqlConnection();
ConnectionStringSettings conString = ConfigurationManager.ConnectionStrings["CSMARTDB"];
this.ConexionTransaccional.ConnectionString = conString.ConnectionString;
this.ConexionTransaccional.Open();
}
public void FinalizarTransaccion()
{
this.ConexionTransaccional.Close();
this.ConexionTransaccional = null;
this.TransaccionAbierta = false;
}
}
}
我修改了 DAL 执行方法以接收该新类的参数,并像这样使用它:
public int InsertarItemDeUnaOrden(int pOrdenDeCompraId, string pCodigoProductoAudifarma, string pCodigoProductoJanssen, string pCodigoEAN13, string pDescripcion, int pCantidadOriginal, decimal pValorUnitario, DAL_Management pManejadorDAL)
{
try
{
DataTable dataTable = new DataTable();
using (SqlConnection conexion = new SqlConnection())
{
using (SqlCommand comando = new SqlCommand())
{
if (pManejadorDAL.TransaccionAbierta == true)
{
comando.Connection = pManejadorDAL.ConexionTransaccional;
}
else
{
ConnectionStringSettings conString = ConfigurationManager.ConnectionStrings["CSMARTDB"];
conexion.ConnectionString = conString.ConnectionString;
conexion.Open();
comando.Connection = conexion;
}
comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = "GOA_InsertarItemDeUnaOrden";
最后,修改了调用类:
DAL.DAL_OrdenDeCompra dalOrdenDeCompra = new GOA.DAL.DAL_OrdenDeCompra();
DAL.DAL_ItemDeUnaOrden dalItemDeUnaOrden = new GOA.DAL.DAL_ItemDeUnaOrden();
using (TransactionScope transaccion = new TransactionScope())
{
DAL.DAL_Management dalManagement = new GOA.DAL.DAL_Management(true);
orden.Id = dalOrdenDeCompra.InsertarOrdenDeCompra(orden.NumeroOrden, orden.PuntoDeEntregaParaLaOrden.Id, (int)orden.TipoDeCompra, orden.FechaOrden, orden.Observaciones, dalManagement);
foreach (ItemDeUnaOrden item in orden.Items)
{
dalItemDeUnaOrden.InsertarItemDeUnaOrden(orden.Id, item.CodigoProductoAudifarma, item.CodigoProductoJanssen, item.CodigoEAN13, item.Descripcion, item.CantidadOriginal, item.ValorUnitario, dalManagement);
}
transaccion.Complete();
}
dalManagement.FinalizarTransaccion();
通过此更改,我将在不启用 MSDTC 的情况下插入订单和商品。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
当针对 SQL Server 2005 使用具有多个连接的
TransactionScope
时,事务将始终升级为分布式事务(意味着将使用 MSDTC)。这是一个已知问题,已在 SQL Server 2008 中修复。
您可以选择的一种选择是编写一个执行所有必需操作的存储过程(折叠
GOA_InsertarOrdenDeCompra
和所有调用GOA_InsertarItemDeUnaOrden
)。对于 SQL Server 2005,这可以通过 XML 参数来完成,尽管 SQL Server 2008(除了不存在此问题之外)具有 表值参数。When using
TransactionScope
with multiple connections against SQL Server 2005, the transaction will always escalate to a distributed one (meaning MSDTC will be used).This is a known issue, fixed in SQL Server 2008.
One option you have is to write a single stored procedure that does all the required operations (folding up
GOA_InsertarOrdenDeCompra
and all callsGOA_InsertarItemDeUnaOrden
). With SQL Server 2005 this can be accomplished with an XML parameter, though SQL Server 2008 (apart from not having this issue) has table-valued parameters.您不能在方法外部创建连接并通过参数将相同的连接传递给这两个方法吗?
这样您就可以使用相同的连接来避免升级。
我的好解决方案是重新考虑 DAL 的架构。
类似于拥有一个中央 DAL,它存储连接对象,并具有对 DAL_OrdenDeCompra 和 DAL_ItemDeUnaOrden 对象的引用,并将 DAL 的引用传递给该对象,以便它们可以与 DAL 中存储的连接进行交互。
然后,DAL 可以有一个带有引用计数、打开增量、关闭减量的 Open 和 Close 方法,并且它应该仅在连接达到零时释放连接,并在增加到 1 时创建一个新连接。此外,DAL 应该实现 IDisposable 来清理连接的资源。然后在您的业务层中执行以下操作:
Can't you create the connection outside the methods and pass the same connection to both methods through the parameters?
That way you use the same connection avoiding the promotion.
My good solution would be to rethink the architecture of the DAL.
Something like having an central DAL, that stores an connection object, and have an reference to your DAL_OrdenDeCompra and DAL_ItemDeUnaOrden objects, and passing the reference of the DAL to this objects so they can interact with the connection stored in the DAL.
And then the DAL could have an Open and Close method with reference count, open increments, close decrements and it should only dispose the connection when it reaches zero and create a new one when incrementing to one. Also the DAL should implement the IDisposable to clean the resources of the connection. Then in your Business Layer you do something like this:
您可以在
DAL.DAL_ItemDeUnaOrden
中有一个方法,它接收ItemDeUnaOrden
的集合而不是单个项目,这样您就可以使用 SqlTransaction (或 TransactionScope)并迭代DA 方法中的项目。根据您的代码,您可能无法访问 DAL 中的繁忙对象 (
ItemDeUnaOrden
),因此您可能需要通过其他方式传递值,可能是 DTO 或DataTable.
You could have a method in
DAL.DAL_ItemDeUnaOrden
which receives a collection ofItemDeUnaOrden
instead of a single item, that way you can use a SqlTransaction (or TransactionScope) and iterate over the items within the DA method.Depending on your code, you might not have access to your busiess objects (
ItemDeUnaOrden
) within you DAL, so you might need to pass the values some other way, maybe DTOs or aDataTable
.