4.4. Programming with iBATIS DataMapper: The .NET API

The IBATIS.NET DataMapper API provides four core functions:

  1. build a SqlMapper instance from a configuration file

  2. execute an update query (including insert and delete).

  3. execute a select query for a single object

  4. execute a select query for a list of objects

The API also provides support for retrieving paginated lists and managing transactions.

4.4.1. Building a SqlMapper Instance

[Important]Important

In prior versions of the DataMapper, the SqlMapper class was responsible for configuration. This has been superceded by a new configuration API found within the DomSqlMapBuilder class. Old configuration method signatures have remained the same, but there are new methods that have been added for more flexibility. These methods support the loading of configuration information through a Stream, Uri, FileInfo, or XmlDocument instance.

An XML document is a wonderful tool for describing a database configuration (Section 4.3) or defining a set of data mappings (Section 3), but you can't execute XML. In order to use the iBATIS.NET configuration and definitions in your .NET application, you need a class you can call.

The framework provides service methods that you can call which read the configuration file (and any of its definition files) and builds a SqlMapper object. The SqlMapper object provides access to the rest of the framework. The SqlMapper is designed to be multi-threaded and long-lived, and so makes for a good singleton. Example 76 shows a singleton Mapper that is bundled with the framework.

Example 4.4. A Mapper singleton you can call from your own applications

[C#]
using IBatisNet.Common.Utilities;
using IBatisNet.DataMapper;
using IBatisNet.DataMapper.Configuration;

namespace IBatisNet.DataMapper
{
 public class Mapper
 {
  private static volatile ISqlMapper _mapper = null;

  protected static void Configure (object obj)
  {
   _mapper = null;
  }

  protected static void InitMapper()
  {
   ConfigureHandler handler = new ConfigureHandler(Configure);
   DomSqlMapBuilder builder = new DomSqlMapBuilder();
   _mapper = builder.ConfigureAndWatch(handler);
  }

  public static ISqlMapper Instance()
  {
   if (_mapper == null)
   {
    lock (typeof (SqlMapper))
    {
     if (_mapper == null) // double-check
     { 
      InitMapper();
     }
    }
   }
   return _mapper;
  }
  
  public static ISqlMapper Get()
  {
   return Instance();
  }
 }
}


To obtain the ISqlMapper instance, just call

[C#]
ISqlMapper mapper = Mapper.Instance();

anywhere in your application, and specify one of the SqlMapper methods (see Section 5.3.2) . Here's an example:

[C#]
IList list = Mapper.Instance().QueryForList("PermitNoForYearList", values);

The first time Mapper.Instance() is called, the DomSqlMapBuilder object will look for the SqlMap.config file in the default location for the type of project it is being used in and build a SqlMapper instance from that configuration. On subsequent calls, the cached mapper instance will be reused. The DomSqlMapBuilder.ConfigureAndWatch() method monitors changes to the configuration files. If the configuration or definitions files change, the SqlMapper will be safely reloaded. This is particularly useful in development, when you might make a change to a data map definition and want to see it take effect without restarting a debugging session. Likewise, in production, it can allow you to make changes to the definitions without reloading the rest of the application.

[Tip]Tip

If you are using NUnit to test your mappings, you can run a test suite, make changes to the XML mapping document, and run the test again. NUnit will reload the configuration automatically.

[Note]Note

The ConfigureAndWatch method requires that your SqlMap.config file and data map files are accessible through the application's file system to be able to track file changes.

If for some reason you do not want to monitor changes to the configuration, you can create your own Mapper class, and use the Configure method instead:

[C#]
ISqlMapper mapper = builder.Configure();

4.4.1.1. Multiple Databases

If you need access to more than one database from the same application, create a DataMapper configuration file for that database and another Mapper class to go with it. In the new Mapper class, change the call to ConfigureAndWatch to

[C#]
ISqlMapper mapper = builder.ConfigureAndWatch("anotherSqlMapConfig.config", handler);

and substitute the name of your configuration file. Each database then has their own singleton you can call from your application:

[C#]
ISqlMapper sqlServer = SqlServerMapper.Get();
ISqlMapper access = AccessMapper.Get();

4.4.1.2. DomSqlMapBuilder Configuration Options

iBATIS offers you a plethora of other options for loading your SqlMap.config file such as loading it through a Stream, Uri, FileInfo, or XmlDocument instance. All of these methods are available through the DomSqlMapBuilder API for creating a SqlMapper instance.

As seen in the prior section, the basic DomSqlMapBuilder.Configure() call will look for a file named SqlMap.config in your application's root directory. This directory's location differs by project type but is normally the directory where you place your web.config or app.config file.

Example 4.5. Basic SqlMapper Configuration Call

ISqlMapper mapper = builder.Configure();

If you have named your configuration file something other than SqlMap.config or if you have located your configuration file in a directory other than the application root directory, you can also pass in a relative or absolute file path to the Configure method.

Example 4.6. SqlMapper Configuration through an absolute or relative file path

/* Configure a SqlMapper from a file path.
   Uses a relative resource path from your application root 
   or an absolute file path such as "file:\\c:\dir\a.config" */
ISqlMapper mapper = builder.Configure(strPath);

[Tip]Tip

Since the application root directory location differs by project type (Windows, Web, or library), you can use an AppSettings key for defining a relative path to your SqlMap.config file. Having this key defined makes it easy to change the path without having to recompile your code:

mapper = builder.Configure(
         ConfigurationSettings.AppSettings["rootPath"]+"SqlMap.config");

Aside from using a simple string filepath, you can also pass in a FileInfo or Uri instance for the DomSqlMapBuilder to use in locating your SqlMap.config file.

Example 4.7. SqlMapper Configuration with a FileInfo or Uri instance

/* Configure a SqlMapper with FileInfo. */
FileInfo aFileInfo = someSupportClass.GetDynamicFileInfo();
ISqlMapper mapper = builder.Configure(aFileInfo);

/* Configure a SqlMapper through a Uri. */
Uri aUri = someSupportClass.GetDynamicUri();
ISqlMapper anotherMapper = builder.Configure(aUri);

If you find that you already have loaded your DataMapper configuration information as an XmlDocument or Stream instance within your application, the DomSqlMapBuilder provides Configure overloads for those types as well.

Example 4.8. SqlMapper Configuration with an XmlDocument or Stream

/* Configure a SqlMapper with an XmlDocument */
XmlDocument anXmlDoc = someSupportClass.GetDynamicXmlDocument();
ISqlMapper mapper = builder.Configure(anXmlDoc);

/* Configure a SqlMapper from a stream. */
Stream aStream = someSupportClass.GetDynamicStream();
ISqlMapper anotherMapper = builder.Configure(aStream);

The DomSqlMapBuilder API provides ConfigureAndWatch methods that can be used to monitor changes to the configuration files. This is particularly useful when using a singleton such as the Mapper class shown in the prior section. The example Mapper singleton allows a reconfigured SqlMapper instance to be reloaded on the fly.

Example 4.9. Mapper ConfigureHandler delegate

...
  protected static void Configure (object obj)
  {
   _mapper = null;
  }

  protected static void InitMapper()
  {
   ConfigureHandler handler = new ConfigureHandler(Configure);
   DomSqlMapBuilder builder = new DomSqlMapBuilder();
   _mapper = builder.ConfigureAndWatch(handler);
  }
...

If you use a custom singleton, you will need to pass a ConfigureHandler (callback delegate) to the DomSqlMapBuilder so that it knows the method for resetting your application's SqlMapper instance. In the Mapper's case, its Configure method is used as the callback delegate.

Since the configuration files need to be watched for changes, your SqlMap.config file must be accessible through the file system. This means that configuration is limited to the three methods shown below.

Example 4.10. DomSqlMapBuilder ConfigureAndWatch methods

/* Configure and monitor the configuration file for modifications 
   and automatically reconfigure the SqlMapper. 
   This basic ConfigureAndWatch method looks for a file with the 
   default name of SqlMap.config in the application root directory. */
public ISqlMapper ConfigureAndWatch(ConfigureHandler configureDelegate)

/* Configure and monitor the configuration file for modifications 
   and automatically reconfigure the SqlMapper. 
   Uses a relative path from your application root 
   or an absolute file path such as "file:\\c:\dir\a.config" */
public ISqlMapper ConfigureAndWatch( string resource, ConfigureHandler configureDelegate )

/* Configure and monitor the configuration file for modifications 
   and automatically reconfigure the SqlMapper. 
   Uses a FileInfo instance for your config file. */
public ISqlMapper ConfigureAndWatch( FileInfo resource, ConfigureHandler configureDelegate )

4.4.1.3. DomSqlMapBuilder : Advanced settings

Before launching the 'Configure' method to build the ISqlMapper instance, you can set those porperties.

Table 4.11. Advanced settings

PropertieDescription
PropertiesAllow to set properties before configuration. Those properties will be added to the properties list defined in the properties.config.
NameValueCollection properties = new NameValueCollection();
properties.Add("connectionString", "...");
builder.Properties = properties;

ISqlMapper mapper = builder.Configure("sqlMap.config");
GetAccessorFactoryAllows to set a custom get accessor factory before configuration, see IGetAccessorFactory interface which defines the contract for the factory responsible to build set accessor for a member object in iBATIS.
SetAccessorFactoryAllows to set a custom set accessor factory before configuration, see ISetAccessorFactory interface which defines the contract for the factory responsible to build get accessor for a member object in iBATIS.
ObjectFactoryAllows to set a custom object factory before configuration, see IObjectFactory interface which defines the contract for the factory responsible for object creation in iBATIS.
SqlMapperAllows to set a custom SqlMapper before configuration, see ISqlMapper interface.
ValidateSqlMapConfigEnable whether or not the validation of configuration document before configuration


4.4.2. Exploring the DataMapper API through the SqlMapper

The ISqlMapper instance acts as a facade to provide access the rest of the DataMapper framework. The DataMapper API methods are shown in Example 4.11.

Example 4.11. The DataMapper API for .NET

[C#]

/* Query API */
public object Insert(string statementName, object parameterObject);
public int Update(string statementName, object parameterObject);
public int Delete(string statementName, object parameterObject);

public object QueryForObject(string statementName, object parameterObject);
public T QueryForObject<T>(string statementName, object parameterObject);
public object QueryForObject(string statementName, object parameterObject, object resultObject);
public T QueryForObject<T>(string statementName, object parameterObject, T resultObject);

public IList QueryForList(string statementName, object parameterObject);
public IList<T> QueryForList<T>(string statementName, object parameterObject);
public void QueryForList(string statementName, object parameterObject, IList resultObject);
public void QueryForList<T>(string statementName, object parameterObject, IList<T> resultObject);
public IList QueryForList(string statementName, object parameterObject, 
                          int skipResults, int maxResults);
public IList<T> QueryForList<T>(string statementName, object parameterObject, 
                          int skipResults, int maxResults);

public IList QueryWithRowDelegate(string statementName, object parameterObject, 
                                  RowDelegate rowDelegate);
public IList<T> QueryWithRowDelegate<T>(string statementName, object parameterObject, 
                                SqlMapper.RowDelegate<T> rowDelegate);

[Obsolete]
public PaginatedList QueryForPaginatedList(String statementName, object parameterObject, 
                                           int pageSize);

public IDictionary QueryForDictionary(string statementName, object parameterObject, 
                                      string keyProperty)
IDictionary<K, V> QueryForDictionary<K, V>(string statementName, object parameterObject, 
                                      string keyProperty);
public IDictionary QueryForDictionary(string statementName, object parameterObject, 
                                      string keyProperty, string valueProperty)
public IDictionary<K, V> QueryForDictionary<K, V>(string statementName, object parameterObject, 
                                      string keyProperty, string valueProperty);
public IDictionary QueryForMap(string statementName, object parameterObject, string keyProperty)
public IDictionary QueryForMap(string statementName, object parameterObject, 
                               string keyProperty, string valueProperty)

/* Connection API */
public void OpenConnection() 
public void CloseConnection()

/* Transaction API */
public void BeginTransaction() 
public void BeginTransaction(bool openConnection) 
public void BeginTransaction(IsolationLevel isolationLevel)
public void BeginTransaction(bool openConnection, IsolationLevel isolationLevel)

public void CommitTransaction()
public void CommitTransaction(bool closeConnection) 

public void RollBackTransaction()
public void RollBackTransaction(bool closeConnection)

Note that each of the API methods accept the name of the Mapped Statement as the first parameter. The statementName parameter corresponds to the id of the Mapped Statement in the Data Map definition (see Section 3.3). In each case, a parameterObject also may be passed. If the Mapped Statement expects no parameters, a null parameterObject may be passed. If a statement does expect parameters, then a valid parameterObject is required. The following sections describe how the API methods work.


[Note]Note

The DatatMapper component store his working ISqlMapSession on different session store.

On Web environnement, the session is stored Http.Request.Items.

On windows environnement on the current thread.

You can configure the session storage by specifing the property ISessionStore on ISqlMapper.

This will allow to set a custom session store like the HybridWebThreadSessionStore This is used for scenarios where most of the you need per request session, but you also does some work outside a request (in a thread pool thread, for instance).

Set it after the configuration and before use of the ISqlMapper.

Example 4.12. Custom ISessionStore

sqlMapper.SessionStore = new HybridWebThreadSessionStore( sqlMapper.Id );
...
sqlMapper.QueryOject(...)
		

4.4.2.1. Insert, Update, Delete

public object Insert(string statementName,
                     object parameterObject);
public int Update(string statementName,
                  object parameterObject);
public int Delete(string statementName,
                  object parameterObject);

If a Mapped Statement uses one of the <insert>, <update>, or <delete> statement-types, then it should use the corresponding API method. The <insert> element supports a nested <selectKey> element for generating primary keys (see Section 3.3.3). If the <selectKey> stanza is used, then Insert returns the generated key; otherwise a null object is returned. Both the Update and Delete methods return the number of rows affected by the statement.

4.4.2.2. QueryForObject

public object QueryForObject(string statementName,
                             object parameterObject);
public object QueryForObject(string statementName, 
                             object parameterObject,
                             object resultObject);

public T QueryForObject<T>(string statementName, object parameterObject);
public T QueryForObject<T>(string statementName, object parameterObject, T resultObject);

If a Mapped Statement is expected to select a single row, then call it using QueryForObject. Since the Mapped Statement definition specifies the result class expected, the framework can both create and populate the result class for you. Alternatively, if you need to manage the result object yourself, say because it is being populated by more than one statement, you can use the alternate form and pass your resultObject as the third parameter.

4.4.2.3. QueryForList

public IList QueryForList(string statementName,
                          object parameterObject);
public void QueryForList(string statementName,
                         object parameterObject,
                         IList resultObject);
public IList QueryForList(string statementName,
                          object parameterObject,
                          int skipResults,
                          int maxResults);

public IList<T> QueryForList<T>(string statementName, object parameterObject);
public void QueryForList<T>(string statementName, 
                                  object parameterObject, 
								  IList<T> resultObject);
public IList<T> QueryForList<T>(string statementName, 
                                            object parameterObject,
                                            int skipResults, int maxResults);						  
						  

If a Mapped Statement is expected to select multiple rows, then call it using QueryForList . Each entry in the list will be an result object populated from the corresponding row of the query result. If you need to manage the resultObject yourself, then it can be passed as the third parameter.

If you need to obtain a partial result, the third form takes the number of records to skip (the starting point) and the maximum number to return, as the skipResults and maxResults parameters. The PaginatedList method provides the same functionality but in a more convenient wrapper.

The QueryWithRowDelegate method also works with multiple rows, but provides a post-processing feature.

4.4.2.4. QueryWithRowDelegate

public delegate void RowDelegate(object obj,
                                 IList list);

public IList QueryWithRowDelegate(string statementName,
                                  object parameterObject, 
                                  RowDelegate rowDelegate);

public IList<T> QueryWithRowDelegate<T>(string statementName, object parameterObject, 
                                SqlMapper.RowDelegate<T> rowDelegate);								  
								  

No matter how well our database is designed or how cleverly we describe our maps, the result objects we get back may not be ideal. You may need to perform some post-processing task on the result objects. You might even want to omit an entry omitted from the list. Or, you might want to use the result object to create some other, more useful object. To save filtering the result objects from to one list to another, you can pass a RowDelegate to the method to do the dirty work. The SqlMapper will go through each of the result objects and give the delegate a chance to modify the object and determine if the object should be added to the IList that will be returned.

[Important]Important

It is your responsibility to add the objects you want returned to the list. If an object is not added, it is not returned.

4.4.2.5.  QueryForMapWithRowDelegate

public delegate void DictionaryRowDelegate(object key, 
                                           object value, 
                                           object parameterObject, 
                                           IDictionary dictionary);

public IDictionary QueryForMapWithRowDelegate(string statementName,
                                              object parameterObject, 
                                              string keyProperty, 
                                              string valueProperty, 
                                              DictionaryRowDelegate rowDelegate);

No matter how well our database is designed or how cleverly we describe our maps, the result objects we get back may not be ideal. You may need to perform some post-processing task on the result objects. You might even want to omit an entry omitted from the dictionary. Or, you might want to use the result object to create some other, more useful object. To save filtering the result objects from to one dictionary to another, you can pass a DictionaryRowDelegate to the method to do the dirty work. The SqlMapper will go through each of the result objects and give the delegate a chance to modify the object and determine if the object should be added to the IDictionary that will be returned.

[Important]Important

It is your responsibility to add the objects you want returned to the dictionary. If an object is not added, it is not returned.

4.4.2.6. QueryForPaginatedList

[Note]Note
The method PaginatedList has been made as obsolete and will not be supported in future version.
public PaginatedList QueryForPaginatedList(string statementName,
                                           object parameterObject,
                                           int pageSize);

We live in an age of information overflow. A database query often returns more hits than users want to see at once, and our requirements may say that we need to offer a long list of results a "page" at a time. If the query returns 1000 hits, we might need to present the hits to the user in sets of fifty, and let them move back and forth between the sets. Since this is such a common requirement, the framework provides a convenience method.

The PaginatedList interface includes methods for navigating through pages (nextPage(), previousPage(), gotoPage()) and also checking the status of the page (isFirstPage(), isMiddlePage(), isLastPage(), isNextPageAvailable(), isPreviousPageAvailable(), getPageIndex(), getPageSize()). Although the total number of records available is not accessible from the PaginatedList interface, this should be easily accomplished by simply executing a second statement that counts the expected results. Too much overhead would be associated with the PaginatedList otherwise.

[Tip]Tip

The PaginatedList method is convenient, but note that a larger set will first be returned by the database provider and the smaller set extracted by the framework. The higher the page, the larger set that will be returned and thrown away. For very large sets, you may want to use a stored procedure or your own query that uses skipResults and maxResults as parameters. Unfortunately, the semantics for the returning partial data sets is not standardized, so PaginatedList is the best we can do within the scope of a framework.

4.4.2.7. QueryForDictionary, QueryForMap

public IDictionary QueryForDictionary(string statementName,
                                      object parameterObject,
                                      string keyProperty)
public IDictionary<K, V> QueryForDictionary<K, V>(string statementName, 
                                      object parameterObject, 
                                      string keyProperty);
public IDictionary QueryForDictionary(string statementName,
                                      object parameterObject,
                                      string keyProperty,
                                      string valueProperty)
public IDictionary<K, V> QueryForDictionary<K, V>(string statementName, 
                                     object parameterObject, 
                                      string keyProperty, 
									  string valueProperty);

public IDictionary QueryForMap(string statementName,
                               object parameterObject, 
                               string keyProperty)
public IDictionary QueryForMap(string statementName, 
                               object parameterObject, 
                               string keyProperty, 
                               string valueProperty)

The QueryForList methods return the result objects within a IList instance. Alternatively, the QueryForDictionary returns a IDictionary instance. The value of each entry is one of the result objects. The key to each entry is indicated by the keyProperty parameter. This is the name of the one of the properties of the result object, the value of which is used as the key for each entry. For example, If you needed a set of Employee objects, you might want them returned as a IDictionary keyed by each object's EmployeeNumber property.

If you don't need the entire result object in your Dictionary, you can add the valueProperty parameter to indicate which result object property should be the value of an entry. For example, you might just want the EmployeeName keyed by EmployeeNumber.

[Important]Important

You do not need to use this method just to obtain an IDictionary result object. As explained in Section 3.5, the result object for any query can be a property object or a IDictionary instance. This method is used to generate a new IDictionary result object from a property object or (another) IDictionary object. In this case, the key is a property you specify, and the value is the row from the result set.

The QueryforMap methods provide the same functionality but under a different name, for the sake of consistency with the Java implementation. (The .NET IDictionary interface is equivalent to the Java Map interface.)

4.4.3. Session

In the iBATIS DataMapper framework, a session is a container for an ADO connection and transaction.

The DataMapper's IDalSession implements the IDisposable interface. So you can use it with the using syntax.

Example 4.13. using instruction

[C#]
using ( IDalSession session = sqlMap.OpenConnection() )
{
  Account account = sqlMap.QueryForObject("GetAccountViaColumnName", 1) as Account;
}

[Note]Note

Sessions cannot be nested. An exception will be thrown if you call BeginTransaction/OpenConnection from the same thread more than once or call CommitTransaction or RollbackTransaction first. In other words, each thread can have at most one session open, per SqlMapper instance.

4.4.4. Connection

The DataMapper API includes methods to demarcate connection boundaries.

// Open a session : Open an ADO connection
public void OpenConnection() 
// Close a session : Close the associated ADO connection
public void CloseConnection()

Example 4.14. Connection example

[C#]
sqlMap.OpenConnection()
Account account = sqlMap.QueryForObject("GetAccountViaColumnName", 1) as Account;
sqlMap.CloseConnection()

// Same thing with using instruction
using ( IDalSession session = sqlMap.OpenConnection() )
{
  Account account = sqlMap.QueryForObject("GetAccountViaColumnName", 1) as Account;
}

4.4.5. Automatic Session

By default, calling any of the API methods (see Section 4.4.2) on a SqlMapper instance will auto-open/close a connection. This means that each call to these methods will be a single unit of work. For many cases, this simple approach may be sufficient. But it is not ideal if you have a number of statements that must execute as a single unit of work, which is to say, succeed or fail as a group. For cases like these, you can use explicit transactions.

An example of using automatic session is shown as Example 4.14.

Example 4.15. Using automatic session

[C#]
Item item = (Item) sqlMap.executeQueryForObject ("GetItem", itemId);
item.Description = "test";
// No session demarcated, so open/close connection will be automatic (implied)
sqlMap.Update("UpdateItem", item);
item.Description = newDescription;
item.Description = "test2";
// No transaction demarcated, so open/close connection will be automatic (implied)
sqlMap.Update("UpdateItem", item);

[Note]Note

Be careful to consider sessions when framing your queries. Automatic sessions are convenient, but you will run into trouble if your unit of work requires more than a single update to the database. In Example 4.14, if the second call to "UpdateItem" fails, the item description will still be updated with the first new description of “TX1”. Not what a user might expect.

4.4.6. Transaction

The DataMapper API includes methods to demarcate transactional boundaries. A transaction can be started, committed and/or rolled back. You can call the transaction methods from the SqlMapper instance.

// Begin a transactional session : Open a connection and begin an ADO transaction
public void BeginTransaction()
// Begin a transactional session : Open a connection is specified and begin an ADO transaction 
public void BeginTransaction(bool openConnection) 
// Begin a transactional session : Open a connection and begin an ADO transaction 
// with the specified IsolationLevel
public void BeginTransaction(IsolationLevel isolationLevel)
// Begin a transactional session : Open a connection is specified and begin an ADO transaction
// with the specified IsolationLevel
public void BeginTransaction(bool openConnection, IsolationLevel isolationLevel)


// Commit a session : Commit the ADO transaction and close the connection  
public void CommitTransaction()
// Commit a session : Commit the ADO transaction and close the connection if specified  
public void CommitTransaction(bool closeConnection)
// RollBack a session : RollBack the ADO transaction and close the connection 
public void RollBackTransaction()
// RollBack a session : RollBack the ADO transaction and close the connection if specified 
public void RollBackTransaction(bool closeConnection)

An example of using transactions is shown as Example 4.15.

Example 4.16. Using transactions

[C#]
try 
  {
      sqlMap.BeginTransaction();
      Item item = (Item) sqlMap.QueryForObject("getItem", itemId);
      item.Description = newDescription;
      sqlMap.Update("updateItem", item);
      sqlMap.CommitTransaction();
  } 
catch {
      sqlMap.RollBackTransaction();
}
 
// With "using" syntax
using ( IDalSession session = sqlMap.BeginTransaction() )
{
  Item item = (Item) sqlMap.QueryForObject("getItem", itemId);
  item.Description = newDescription;
  sqlMap.Update("updateItem", item);

  session.Complete(); // Commit
}

4.4.7. Distributed Transactions

Distributed transactions are transactions that can span multiple resource managers, such as SQL Server and Oracle, and reconcile transactions among them.

iBATIS.NET introduces a new TransactionScope class mimicking the new TransactionScope found in the System.Transactions namespace (.NET Framework 2.0). This class supports MSMQ, ADO.NET, SqlServer, and DTC transaction models. This is a simple managed interface to COM+'s SWC (Services Without Components) Transactions. It can be used only by developers using .NET 1.1 and Windows XP SP2 or Windows Server 2003 since it implements distributed transactional support using the ServiceDomain class.

Usage is simple, as seen in the following example where a code block is made transactional à la Indigo (moving to Indigo will be easier since it is the same API):

Example 4.17. Using distributed transactions

[C#]
using IBatisNet.Common.Transaction;

using (TransactionScope tx = new TransactionScope())
{
   sqlMapSqlServer.OpenConnection();
    // Transaction will be automatically associated
   account = sqlMapSqlServer.QueryForObject("GetAccount", accountId) as Account;
   account.FirstName = "Gilles";
   sqlMapSqlServer.Update(account);
   sqlMapSqlServer.CloseConnection();

   sqlMapOracle.OpenConnection();
   // Transaction will be automatically associated
   product = sqlMapOracle.QueryForObject("GetProduct", productId) as Product;
   product.Quantity = 1000;
   sqlMapOracle.Update(product);
   sqlMapOracle.CloseConnection();

   tx.Complete(); // Commit
}

It is important to make sure that each instance of this class gets Close()'d. The easiest way to ensure that each instance is closed is with the using statement in C#. When Dispose is called on the transaction scope at the end of the using code block, the ambient transaction will be commited only if the Complete() method has been called.


[Note]Note

This TransactionScope class does not support a nested transaction scope with different transaction options.

4.4.8. Coding Examples [TODO: Expand in to a Cookbook of practical examples]

Example 4.18. Executing Update (insert, update, delete)

[C#]
Product product = new Product();
product.Id = 1;
product.Description = “Shih Tzu”;
int key = sqlMap.Insert (“insertProduct”, product);


Example 4.19. Executing Query for Object (select)

[C#]
int key = 1;
Product product = sqlMap.QueryForObject (“getProduct”, key) as Product;


Example 4.20. Executing Query for Object (select) With Preallocated Result Object

[C#]
Customer customer = new Customer();
sqlMap.BeginTransaction();
sqlMap.QueryForObject(“getCust”, parameterObject, customer);
sqlMap.QueryForObject(“getAddr”, parameterObject, customer);
sqlMap.CommitTransaction();


Example 4.21. Executing Query for List (select)

[C#]
IList list = sqlMap.QueryForList (“getProductList”, null);


Example 4.22. Auto-Open/Close

[C#]
// When OpenConnection is not called, the statements will auto-Open/Close. 
int key = sqlMap.Insert (“insertProduct”, product);


Example 4.23. Executing Query for List (select) With Result Boundaries

[C#]
List list = sqlMap.queryForList (“getProductList”, null, 0, 40);


Example 4.24. Executing Query with a RowHandler (select)

[C#]
public void RowHandler(object obj, IList list)
{
  Product product = (Product) object;
  product.Quantity = 10000;
}

SqlMapper.RowDelegate handler = new SqlMapper.RowDelegate(this.RowHandler);
IList list = sqlMap.QueryWithRowDelegate("getProductList", null, handler);


Example 4.25. Executing Query for Paginated List (select)

[C#]
PaginatedList list = sqlMap.QueryForPaginatedList (“getProductList”, null, 10);
list.NextPage();
list.PreviousPage();


Example 4.26. Executing Query for Map

[C#]
IDictionary map = sqlMap.QueryForMap (“getProductList”, null, “productCode”);
Product p = (Product) map[“EST-93”];