public void ProcessAndCompleteOrder(OrderDataModel order) { try { dalOrder = Trade.DALFactory.Order.Create(Settings.DAL); dalOrder.Open(Settings.TRADEDB_SQL_CONN_STRING); decimal total = 0; int holdingid = -1; QuoteDataModel quote = dalOrder.getQuoteForUpdate(order.symbol); //Get the latest trading price--this is the money going into (or out of) the users account. order.price = quote.price; //Calculate order total, and create/update the Holding. Whole holding //sells delete the holding, partials sells update the holding with new amount //(and potentially the order too), and buys create a new holding. if (order.orderType == StockTraderUtility.ORDER_TYPE_BUY) { holdingid = dalOrder.createHolding(order); total = Convert.ToDecimal(order.quantity) * order.price + order.orderFee; } else if (order.orderType == StockTraderUtility.ORDER_TYPE_SELL) { holdingid = sellHolding(order); //We will assume here, since the holding cannot be found, it has already been sold, //perhaps in another browser session. if (holdingid == -1 ) return; total = -1 * Convert.ToDecimal(order.quantity) * order.price + order.orderFee; } //Debit/Credit User Account. Note, if we did not want to allow unlimited margin //trading, we would change the ordering a bit and add the biz logic here to make //sure the user has enough money to actually buy the shares they asked for! //Now Update Account Balance. dalOrder.updateAccountBalance(order.accountID, total); //Update the stock trading volume and price in the quote table dalOrder.updateStockPriceVolume(order.quantity, quote); //Now perform our ACID tx test, if requested based on order type and acid stock symbols if (order.symbol.Equals(StockTraderUtility.ACID_TEST_BUY) && order.orderType == StockTraderUtility.ORDER_TYPE_BUY) throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_ACID_BUY); else if (order.symbol.Equals(StockTraderUtility.ACID_TEST_SELL) && order.orderType == StockTraderUtility.ORDER_TYPE_SELL) throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_ACID_SELL); //Finally, close the order order.orderStatus = StockTraderUtility.ORDER_STATUS_CLOSED; order.completionDate = DateTime.Now; order.holdingID = holdingid; dalOrder.closeOrder(order); //Done! return; } catch { throw; } finally { dalOrder.Close(); } }
/// <summary> /// This is the business logic for placing orders, handling txs. /// StockTrader allows the user to select whether to use System.Transactions /// or use ADO.NET Provider-supplied transactions for order placement (buy, sell); and new user registrations. /// The best choice for performance will vary based on the backend database being used with the /// application. /// </summary> /// <param name="orderType">Buy or sell type.</param> /// <param name="userID">User id to create/submit order for.</param> /// <param name="holdingID">Holding id to sell.</param> /// <param name="symbol">Stock symbol to buy.</param> /// <param name="quantity">Shares to buy.</param> public OrderDataModel placeOrder(string orderType, string userID, int holdingID, string symbol, double quantity) { OrderDataModel order = null; HoldingDataModel holding = new HoldingDataModel(); switch (Settings.TRANSACTION_MODEL) { case (StockTraderUtility.TRANSACTION_MODEL_SYSTEMDOTTRANSACTION_TRANSACTION): { //This short try/catch block is introduced to deal with idle-timeout on SQL Azure //connections. It may not be required in the near future, but as of publication //SQL Azure disconnects idle connections after 30 minutes. While command retry-logic //in the DAL automatically deals with this, when performing a tx, with the BSL handling //tx boundaries, we want to go into the tx with known good connections. The try/catch below //ensures this. try { dalOrder = Trade.DALFactory.Order.Create(Settings.DAL); dalOrder.Open(Settings.TRADEDB_SQL_CONN_STRING); dalOrder.getSQLContextInfo(); } catch { } finally { dalOrder.Close(); } System.Transactions.TransactionOptions txOps = new TransactionOptions(); txOps.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted; txOps.Timeout = TimeSpan.FromSeconds(Settings.SYSTEMDOTTRANSACTION_TIMEOUT); //Start our System.Transactions tx with the options set above. using (TransactionScope tx = new TransactionScope(TransactionScopeOption.Required, txOps)) { dalOrder.Open(Settings.TRADEDB_SQL_CONN_STRING); try { //Business Step 1: create the order header. order = createOrder(orderType, userID, holdingID, symbol, quantity, ref holding); //Business Step 2: Determine which order processing mode to use, //and either process order right away (sync) and in-process with //calling ASP.NET Web app; or interface with the //async WCF Order Processing Service (cooler) via a service-to-service call, //distributed/remote. if (Settings.ORDER_PROCESSING_MODE != StockTraderUtility.OPS_INPROCESS) { //Fire up our async client; we follow the same model here as with the //StockTrader Web App in that we do not talk 'directly' to the generated proxy //for the service; rather we channel all calls through a single //class that then talks to the service proxy. This will aid in more //easily knowing where communication and proxy logic sits; and make changes to services //you might want to interface with vs. littering proxy calls throughout the //business tier itself. TradeOrderServiceAsyncClient asyncclient = new TradeOrderServiceAsyncClient(Settings.ORDER_PROCESSING_MODE, new Settings()); asyncclient.processOrderASync(order); } else { processOrderSync(order, holding); } //Commit! tx.Complete(); return order; } //If per chance you are doing step-through debugging through here and are getting a // "TRANSACTION HAS ABORTED" exception and do not know why, //it's quite likely you are hitting the 15-second timeout we set in //ConfigurationSettings for the System.Transaction options so its just doing what we told it to. //Simply adjust timeout higher, recompile if you need to. catch { throw; } finally { dalOrder.Close(); } } } //Repeat for ADO.NET transactions config option case. case (StockTraderUtility.TRANSACTION_MODEL_ADONET_TRANSACTION): { if (Settings.ORDER_PROCESSING_MODE == StockTraderUtility.OPS_SELF_HOST_MSMQ) goto case StockTraderUtility.TRANSACTION_MODEL_SYSTEMDOTTRANSACTION_TRANSACTION; dalOrder.Open(Settings.TRADEDB_SQL_CONN_STRING); dalOrder.BeginADOTransaction(); try { //Business Step 1: create the order header. order = createOrder(orderType, userID, holdingID, symbol, quantity, ref holding); //Business Step 2: Determine which order processing mode to use, //and either process order right away (sync); or interface with the //async WCF Order Processing Service (cooler) via a service-to-service call. if (Settings.ORDER_PROCESSING_MODE != StockTraderUtility.OPS_INPROCESS) { //Fire up our async client; we follow the same model here as with the //StockTrader Web App in that we do not talk 'directly' to the generated proxy //for the service; rather we channel all calls through a single //class that then talks to the service proxy. This will aid in more //easily knowing where communication and proxy logic sits; and make changes to services //you might want to interface with vs. littering proxy calls throughout the //business tier itself. TradeOrderServiceAsyncClient asyncclient = new TradeOrderServiceAsyncClient(Settings.ORDER_PROCESSING_MODE, new Settings()); asyncclient.processOrderASync(order); dalOrder.CommitADOTransaction(); } else { processOrderSync(order, holding); } dalOrder.CommitADOTransaction(); return order; } catch { dalOrder.RollBackTransaction(); throw; } finally { dalOrder.Close(); } } } throw new Exception(Settings.ENABLE_GLOBAL_SYSTEM_DOT_TRANSACTIONS_CONFIGSTRING + ": " + StockTraderUtility.EXCEPTION_MESSAGE_INVALID_TXMMODEL_CONFIG + " Repository ConfigKey table."); }
/// <summary> /// SubmitOrder service operation is a service operation that processes order messages from the client /// coming in via TCP, HTTP, or a non-transacted (volatile) MSMQ WCF binding from either the BSL or another remote instance /// </summary> /// <param name="order">Order to submit.</param> public void SubmitOrder(OrderDataModel order) { System.Transactions.TransactionOptions txOps = new TransactionOptions(); txOps.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted; txOps.Timeout = TimeSpan.FromSeconds(Settings.SYSTEMDOTTRANSACTION_TIMEOUT); try { dalOrder = Trade.DALFactory.Order.Create(Settings.DAL); dalOrder.Open(Settings.TRADEDB_SQL_CONN_STRING); dalOrder.getSQLContextInfo(); } catch { } finally { dalOrder.Close(); } // Start our System.Transactions tx with the options set above. using (TransactionScope tx = new TransactionScope(TransactionScopeOption.Required, txOps)) { try { processOrder(order); //Complete transaction scope! tx.Complete(); } catch (Exception e) { string innerException = null; if (e.InnerException != null) innerException = e.InnerException.ToString(); ConfigUtility.writeErrorConsoleMessage("Error Completing Order! Exception: " + e.ToString() + "\nInner Exception: " + innerException,EventLogEntryType.Error,true, new Settings()); throw; } } return; }