使用TransactionScope时避免启用MSDTC

时间:2011-08-31 14:24:06

标签: c# transactions data-access-layer transactionscope

[使用:C#3.5 + SQL Server 2005]

我在Business Layer中有一些代码,它们在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的情况下插入订单和商品。

3 个答案:

答案 0 :(得分:5)

当对TransactionScope使用针对SQL Server 2005的多个连接时,事务将始终升级为分布式事务(意味着将使用MSDTC)。

这是known issue,已在SQL Server 2008中修复。

您拥有的一个选项是编写一个执行所有必需操作的存储过程(折叠GOA_InsertarOrdenDeCompra和所有调用GOA_InsertarItemDeUnaOrden)。使用SQL Server 2005,可以使用XML参数完成此操作,但SQL Server 2008(除了没有此问题)具有table-valued parameters

答案 1 :(得分:3)

您不能在方法之外创建连接,并通过参数将相同的连接传递给两个方法吗?

这样您就可以使用相同的连接来避免促销。

我的好解决方案是重新考虑DAL的架构。 类似于拥有中央DAL,存储连接对象,并且引用DAL_OrdenDeCompra和DAL_ItemDeUnaOrden对象,并将DAL的引用传递给此对象,以便它们可以与存储在DAL中的连接进行交互。 然后DAL可以有一个Open和Close方法,引用计数,打开增量,关闭递减,它应该只在达到零时处理连接,并在递增到1时创建一个新连接。此外,DAL应实现IDisposable以清除连接的资源。然后在您的业务层中执行以下操作:

using(DAL dal = new DAL())
{
    DAL.DAL_OrdenDeCompra dalOrdenDeCompra = dal.OrdenDeCompra;
    DAL.DAL_ItemDeUnaOrden dalItemDeUnaOrden = dal.ItemDeUnaOrden;
    using (TransactionScope transaccion = new TransactionScope())
    {
        dal.Open();
        //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;
}

答案 2 :(得分:1)

您可以在DAL.DAL_ItemDeUnaOrden中获得一个方法,该方法接收ItemDeUnaOrden而不是单个项目的集合,这样您就可以使用SqlTransaction(或TransactionScope)并迭代DA方法中的项目

orden.Id = dalOrdenDeCompra.InsertarOrdenDeCompra(...);
dalItemDeUnaOrden.InsertarVariosItemsDeUnaOrden(orden.Items);

根据您的代码,您可能无法访问DAL中的业务对象(ItemDeUnaOrden),因此您可能需要以其他方式传递值,可能是DTO或DataTable