The IBATIS.NET DataMapper API provides four core functions:
build a SqlMapper
instance from a
configuration file
execute an update query (including insert and delete).
execute a select query for a single object
execute a select query for a list of objects
The API also provides support for retrieving paginated lists and managing transactions.
Important | |
---|---|
In prior versions of the DataMapper, the
|
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 | |
---|---|
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 | |
---|---|
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();
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();
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.
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 | |
---|---|
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 )
Before launching the 'Configure' method to build the ISqlMapper instance, you can set those porperties.
Table 4.11. Advanced settings
Propertie | Description |
---|---|
Properties | Allow 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"); |
GetAccessorFactory | Allows 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. |
SetAccessorFactory | Allows 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. |
ObjectFactory | Allows to set a custom object factory before configuration, see IObjectFactory interface which defines the contract for the factory responsible for object creation in iBATIS. |
SqlMapper | Allows to set a custom SqlMapper before configuration, see ISqlMapper interface. |
ValidateSqlMapConfig | Enable whether or not the validation of configuration document before configuration |
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.
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.
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.
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.
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 | |
---|---|
It is your responsibility to add the objects you want returned to the list. If an object is not added, it is not returned. |
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 | |
---|---|
It is your responsibility to add the objects you want returned to the dictionary. If an object is not added, it is not returned. |
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 | |
---|---|
The |
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 | |
---|---|
You do not need to use this method just to obtain an
|
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.)
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 | |
---|---|
Sessions cannot be nested. An exception will be thrown if you
call
|
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; }
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 | |
---|---|
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
|
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 }
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 | |
---|---|
This |
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”];