using System; using System.Collections; using System.Data; using System.Data.SqlClient; using System.Text.RegularExpressions; using System.Threading; using System.Transactions; using MbUnit.Framework; using Northwind; namespace SubSonic.Tests { /// /// Tests SubSonics behavior with regard to using TransactionScope and SharedDbConnectionScope /// when the DTC is turned off. /// [TestFixture] public class TransactionWithDtcOffTests { private const int MaxRandomNumber = 10000; private readonly Regex _dtcErrorMessage = new Regex("MSDTC on server '.*' is unavailable"); /// /// Used to generate random numbers that are embedded in strings that get presisted to the database /// private readonly Random _rand = new Random(); private readonly MsDtcService msdtc = new MsDtcService(); /// /// Tests the fixture set up. /// [TestFixtureSetUp] public void TestFixtureSetUp() { msdtc.Stop(); } /// /// Tests the fixture tear down. /// [TestFixtureTearDown] public void TestFixtureTearDown() { msdtc.Revert(); } /// /// Noes the transaction scope_ can retrieve single product. /// [Test] public void NoTransactionScope_CanRetrieveSingleProduct() { Product p1 = new Product(1); Assert.AreEqual(1, p1.ProductID); } /// /// Noes the transaction scope_ can retrieve multiple products. /// [Test] public void NoTransactionScope_CanRetrieveMultipleProducts() { Product p1 = new Product(1); Product p2 = new Product(2); Assert.AreEqual(1, p1.ProductID); Assert.AreEqual(2, p2.ProductID); } /// /// Retrieves the multiple products_ fails without shared connection. /// [Test] public void RetrieveMultipleProducts_FailsWithoutSharedConnection() { string errorMessage = String.Empty; try { using(TransactionScope ts = new TransactionScope()) { Product p1 = new Product(1); Product p2 = new Product(2); Assert.AreEqual(1, p1.ProductID); Assert.AreEqual(2, p2.ProductID); } } catch(SqlException e) { errorMessage = e.Message; } Assert.IsTrue(_dtcErrorMessage.IsMatch(errorMessage), errorMessage); } /// /// Determines whether this instance [can retrieve multiple entities_ fails without shared connection scope]. /// [Test] public void CanRetrieveMultipleEntities_FailsWithoutSharedConnectionScope() { string errorMessage = String.Empty; try { using(TransactionScope ts = new TransactionScope()) { Product p1 = new Product(1); Product p2 = new Product(2); Order o1 = new Order(1); Order o2 = new Order(2); Assert.AreEqual(1, p1.ProductID); Assert.AreEqual(2, p2.ProductID); Assert.AreEqual(1, o1.OrderID); Assert.AreEqual(2, o2.OrderID); } } catch(SqlException e) { errorMessage = e.Message; } Assert.IsTrue(_dtcErrorMessage.IsMatch(errorMessage)); } /// /// Determines whether this instance [can retrieve multiple entities_ succeeds with shared connection scope]. /// [Test] public void CanRetrieveMultipleEntities_SucceedsWithSharedConnectionScope() { using(TransactionScope ts = new TransactionScope()) { using(SharedDbConnectionScope connScope = new SharedDbConnectionScope()) { Product p1 = new Product(1); Product p2 = new Product(2); Order o1 = new Order(10248); Order o2 = new Order(10249); Assert.AreEqual(1, p1.ProductID); Assert.AreEqual(2, p2.ProductID); Assert.AreEqual(10248, o1.OrderID); Assert.AreEqual(10249, o2.OrderID); } } } /// /// Updates the single product_ succeeds using shared connection. /// [Test] public void UpdateSingleProduct_SucceedsUsingSharedConnection() { using(TransactionScope ts = new TransactionScope()) { using(SharedDbConnectionScope connScope = new SharedDbConnectionScope()) SaveProduct(1, "new name of product"); } } /// /// Updates the single product retrieve multiple products_ fails without shared connection. /// [Test] public void UpdateSingleProductRetrieveMultipleProducts_FailsWithoutSharedConnection() { string errorMessage = String.Empty; try { using(TransactionScope ts = new TransactionScope()) { SaveProduct(1, "new name of product"); Product p2 = new Product(2); Product p3 = new Product(3); } } catch(SqlException e) { errorMessage = e.Message; } Assert.IsTrue(_dtcErrorMessage.IsMatch(errorMessage)); } /// /// Updates the single product retrieve multiple products_ succeeds with shared connection. /// [Test] public void UpdateSingleProductRetrieveMultipleProducts_SucceedsWithSharedConnection() { string p1OriginalProductName = new Product(1).ProductName; using(TransactionScope ts = new TransactionScope()) { using(SharedDbConnectionScope connScope = new SharedDbConnectionScope()) { SaveProduct(1, "new name of product"); Product p2 = new Product(2); Product p3 = new Product(3); } } Assert.AreEqual(p1OriginalProductName, new Product(1).ProductName, "Transaction NOT rolled back"); } /// /// Updates the single product_ commit change to database. /// [Test] public void UpdateSingleProduct_CommitChangeToDatabase() { string newProductName = "new name of product 20: " + _rand.Next(MaxRandomNumber); using(TransactionScope ts = new TransactionScope()) { using(SharedDbConnectionScope connScope = new SharedDbConnectionScope()) { SaveProduct(20, newProductName); Product p2 = new Product(2); Product p3 = new Product(3); ts.Complete(); } } Assert.AreEqual(newProductName, new Product(20).ProductName, "Transaction Not Committed"); } /// /// Updates the multiple products_ fails without shared connection. /// [Test] public void UpdateMultipleProducts_FailsWithoutSharedConnection() { string errorMessage = String.Empty; try { using(TransactionScope ts = new TransactionScope()) { SaveProduct(1, "new name of product 1"); SaveProduct(2, "new name of product 2"); } } catch(SqlException e) { errorMessage = e.Message; } Assert.IsTrue(_dtcErrorMessage.IsMatch(errorMessage)); } /// /// Updates the multiple products_ succeeds with shared connection. /// [Test] public void UpdateMultipleProducts_SucceedsWithSharedConnection() { string p1OriginalProductName = new Product(1).ProductName; string p2OriginalProductName = new Product(2).ProductName; using(TransactionScope ts = new TransactionScope()) { using(SharedDbConnectionScope connScope = new SharedDbConnectionScope()) { SaveProduct(1, "new name of product 1"); SaveProduct(2, "new name of product 2"); } } Assert.AreEqual(p1OriginalProductName, new Product(1).ProductName, "Transaction NOT rolled back"); Assert.AreEqual(p2OriginalProductName, new Product(2).ProductName, "Transaction NOT rolled back"); } /// /// Determines whether this instance [can nest shared connections]. /// [Test] public void CanNestSharedConnections() { SortedList originalNames = new SortedList(); for(int i = 1; i <= 4; i++) originalNames[i] = new Product(i).ProductName; using(TransactionScope outerTransactionScope = new TransactionScope()) { using(SharedDbConnectionScope outerConnectionScope = new SharedDbConnectionScope()) { Product p1 = UpdateProduct(1, "new name of product 1: " + _rand.Next(MaxRandomNumber)); Product p2 = UpdateProduct(2, "new name of product 2: " + _rand.Next(MaxRandomNumber)); using(TransactionScope innerTransactionScope = new TransactionScope()) { using(SharedDbConnectionScope innerConnectionScope = new SharedDbConnectionScope()) { Assert.AreSame(outerConnectionScope.CurrentConnection, innerConnectionScope.CurrentConnection); SaveProduct(3, "new name of product 3: " + +_rand.Next(MaxRandomNumber)); SaveProduct(4, "new name of product 4: " + +_rand.Next(MaxRandomNumber)); innerTransactionScope.Complete(); } } // ensure the connection hasn't been disposed by the inner scope Assert.IsTrue(outerConnectionScope.CurrentConnection.State == ConnectionState.Open); p1.Save(); p2.Save(); } } for(int i = 1; i <= 4; i++) Assert.AreEqual(originalNames[i], new Product(i).ProductName, "Product {0} is incorrect", i); } /// /// Multis the threaded test. /// [Test] public void MultiThreadedTest() { // TODO: this should be improved to wait for threads to complete and consolidate any error messages. // Right now, if there is a problem, this test will succeed and (a) other tests will fail (b) the VsTestHost.exe // will fail with an unhandled exception. const int iterations = 100; for(int i = 0; i < iterations; i++) ThreadPool.QueueUserWorkItem(ThreadingTarget); } /// /// Threadings the target. /// /// The state. public void ThreadingTarget(object state) { string p1OriginalProductName = new Product(1).ProductName; string p2OriginalProductName = new Product(2).ProductName; using(TransactionScope ts = new TransactionScope()) { using(SharedDbConnectionScope connScope = new SharedDbConnectionScope()) { SaveProduct(1, "new name of product 1"); SaveProduct(2, "new name of product 2"); } } Assert.AreEqual(p1OriginalProductName, new Product(1).ProductName); Assert.AreEqual(p2OriginalProductName, new Product(2).ProductName); } /// /// Saves the product. /// /// The product id. /// Name of the product. private static void SaveProduct(int productId, string productName) { Product p1 = UpdateProduct(productId, productName); p1.Save("Unit Test"); } /// /// Updates the product. /// /// The product id. /// Name of the product. /// private static Product UpdateProduct(int productId, string productName) { Product p1 = new Product(productId); p1.ProductName = productName; p1.UnitPrice = 50; return p1; } } }