views:

570

answers:

3

I have some basic confusion about how transactions and msdtc work together.

I have a basic server/client winforms app. The app uses transactionscope to encapsulate several sql commands that are executed on the sql server.

The app seemed to work fine when I enabled msdtc network access on the server only. Then one day it stopped working saying network access was not enabled.

Now it seems that I have to enable msdtc network access on both the client computer and server for transactionscope to work.

Does the client or server msdtc service do the transaction work? Or maybe its both?

Does anyone have guidance on whether msdtc network access is needed on both client and server or just server?

+1  A: 

If you are using MSDTC, then you will need the client (your application) and the server (database) to both run MSDTC and also to be configured properly.

This can be source of pain especially when dealing with firewalls. If you are having trouble see Troubleshooting Problems with MSDTC. It talks about BizTalk but it applies to MSDTC in general. DTCPING is also your friend.

Now if you are using SQL Server 2005 and higher, are accessing only one database, are using one database connection and are not passing Transactions between app domains then you should not require the use of MSDTC. Under those circumstances the System.Transactions transaction manager will manage your transactions for you. If any of the previous situations occurs then the transaction will be promoted to a distributed transaction (and the transaction manager will be MSDTC). See Transaction Management Escalation for more information.

In general it is best to avoid the use of MSDTC if you do not need it. i.e. if you are only dealing with a single SQL Server 2005+ database then try to design your code to not use MSDTC. Besides the configuration hassle, DTC imposes a performance penalty because all calls to MSDTC are out of process combined with the overhead of the two phase commit protocol (which MSDTC uses).

In terms of what is happening in your specific situation it is hard to say. If your code hasn't changed, then perhaps the firewall rules have changed? I have also seen Windows Updates change DTC configuration (for security) which caused a problem.

Update Based on Comment:

For monitoring transaction promotion or escalation, if you are not using any distributed transactions I think you could use some of the Distributed Transaction Coordinator performance counters to track committed transactions. If testing you could disable MSDTC and see if your code fails. Another way would be to monitor transactions in SQL Server. From a coding perspective you could try to handle the DistributedTransactionStarted event and do some logging (but remove that code before going to production).

For a code example using a single connection go to the TransactionScope page at MSDN. Basically, create a TransactionScope, create a SqlConnection, do some work with the SqlConnection, close the connection, call scope.Complete().

Note that if you are using Data Adapter methods, they automatically manage your connection so the connection is closed or returned to the connection pool. Either way, if another operation is called then the transaction will be promoted to a DTC transaction. See System.Transactions and connection pooling for more details.

Tuzo
Thanks for the great info. I would love to not use MSDTC. I'm using sql2005, 1 db, not passing app domains, but not sure if I'm using 1 connection.Would it be possible to give a short example of a transaction with multiple sql statements that would not be elevated to dtc?Is there a tool or any other way to tell if my transactions are being elevated to msdtc?
muhan
+1  A: 

To expand on @Tuzo's explanation, here is an example of a command that will always escalate:

using(var scope = new TransactionScope())
{
  using(var conn = new SqlConnection(connString)){
     conn.Open();
     //...some command, etc.
  }
  using(var conn = new SqlConnection(connString)){
     conn.Open();
     //...some command, etc.
  }
  scope.Complete();
}

In practice, the connection and command will be in another class, etc., but you get the idea. Even if the connection string is to the same database, it will escalate using DTC, because it's two connections. A non-escalating transaction would be:

using(var scope = new TransactionScope())
{
  using(var conn = new SqlConnection(connString)){
     conn.Open();
     //...some command, etc.
     //...some other command, etc.
  }
  scope.Complete();
}

This is better code anyway, because you open the connection, do what you need, and close as soon as possible. This does mean you have to think through your connection management. Depending on your app, you may implement this differently. For example:

using(var scope = new TransactionScope())
using(var conn = new SqlConnection(connString))
{
    conn.Open();
    var myService = new MyService(conn);
    var myService2 = new MyService2(conn);
    myService.DoSomething();
    myService2.DoSomething();
    scope.Complete();
}

There are various ways to implement this. Enterprise Library Data Access Application Block, and various ORMs may also help you handle your connections and transactions more efficiently.

Daniel
Enterprise Library Data Access Block can help because it maintains an internal list of active transactions and their connections (in the TransactionScopeConnections class). The same connection is used for the lifetime of the transaction so the transaction does not escalate to a distributed transaction. It also has the benefit of making your interfaces cleaner since you do not need to pass the connection around to every method that needs to use a database connection.
Tuzo
A: 

Update: I found an article that explains why transactions are being promoted form LTM to MSDTC when only usign both GetData and Update on the same data adapter within a TransactionScope along with a workaround.

The definitive TableAdapters + Transactions blog post http://blah.winsmarts.com/2006/06/18/the-definitive-tableadapters--transactions-blog-post.aspx

I understand the part about having multiple connections open at once escalating a transaction to be distributed. However, I'm having a problem where there is only one connection, and one query against a database that is escalating it. There are not any transactions in the stored procedure either. If anyone has a clue, I'd like to hear of it. From my code example, the "adapter.Update(table)" will trigger a distributed transaction.

I've brought the guts of the code out of my existing project and simplified most of what was going on, and I am still having the same problems. This is basically creating a dataset with a table adapter and setting it up with a stored procedure to select, insert, and delete. I select all related records with a specific user. Then, depending on if a "myPPID" exists for one of the records, I add it or delete it. I then call the update method and see the transaction escalate to be distributed by watching the Transaction Statistics in component services.

I'm using windows XP Pro SP3 and .Net Framework 3.5 for the client program. It connects to a SQL 2005 database over the LAN to Windows Server 2003 R2 Enterprise Edition SP2.

private void button1_Click(object sender, EventArgs e)
{
int userId = 3;
int myPPId = 881;
using (TransactionScope ts = new TransactionScope())
{
    using (DataSet1TableAdapters.AssignedPPTableAdapter adapter 
    = new MSDTCPromotionTest.DataSet1TableAdapters.AssignedPPTableAdapter())
    {
        using (DataSet1.AssignedPPDataTable table = adapter.GetData(userId))
        {
            DataSet1.AssignedPPRow row = table.FindByUserIdmyPPId(
                userId, myPPId);
            if (row == null)
            {
                table.AddAssignedPPRow(userId, myPPId, string.Empty, 
                    string.Empty, true);
            }
            else
            {
                row.Delete();
            }
            adapter.Update(table);
        }
        ts.Complete();
    }
}
}

The connection string is nothing special:

<add name="ConnectionString" connectionString="
Data Source=devdb;
Initial Catalog=&quot;TEST MSDTC&quot;;
Integrated Security=True"
providerName="System.Data.SqlClient" />

Also, the stored procedures are simple crud calls.

Create:

ALTER procedure [dbo].[p_UserForm_AssignedPP_Insert]
(
    @UserId INT,
    @myPPId int
)
AS
SET NOCOUNT ON;
INSERT INTO [UsermyPP] ([UserID],[myPPID],[DateCreated])
     VALUES (@UserId,@myPPId,GETutcDATE()) 

Read:

ALTER procedure [dbo].[p_UserForm_AssignedPP_SelectByUserId]
(
    @UserId int
)
AS
SELECT  
    [UserId],
    [myPPId], 
    '' Title,
    '' Abbreviation,
    0 IsArchived
from
    UsermyPP  unpp
where
    unpp.[userid] = @UserId

Delete:

ALTER procedure [dbo].[p_UserForm_AssignedPP_Delete]
(
    @Original_UserId INT,
    @Original_MyPPId INT
)
AS
SET NOCOUNT ON;
DELETE FROM usermypp WHERE [UserID] = @Original_UserId 
    AND [MyPPID] = @Original_MyPPId
Lewis Moten