Using raw DataSet
objects directly in the presentation layer is not a good practice and should be avoided whenever possible. It makes maintenance a relatively difficult job later on when there are changes to the underlying data tier.
Consider the following code snippet. It retrieves a dataset from the data layer and references the data inside using table column names.
DataSet _accountsDataset= GetAccountsList(); Datarow _row = _accountsDataset.Tables["Accounts"].Rows[0]; txtFirstName.text = _row["FirstName"]; txtLastName.text = _row["LastName"];
Now, try to imagine this same code snippet peppered all over your application across your forms, custom controls, and user controls. This will translate into a lot of trouble when the underlying database column name or type changes in the future. Many programmers commonly make the mistake of passing dataset objects directly to the presentation tier (a classic example of this is the Datagrid
control, which readily takes in and renders a DataSet object).
A logic or business object tier shields the presentation tier from having to worry about the underlying data tier.
The great thing about data binding is that it not only works on datasets but also on custom business object classes. This allows you to wrap or encapsulate the datasets within your own custom class and to expose each field as properties in your class. The following diagram best illustrates this concept:
Let's take a look at what happens when the underlying database column or type changes this time. If the Database Administrator decided to rename the FirstName
column as IndividualFirstName
one day, you would only need to change the property definition in your logic tier to the following:
public string FirstName { get {return_row["IndividualFirstName"]; }}
The following table lists the various business objects you will need to build in the sales force application:
Class name |
Description |
---|---|
|
This is the base class for the |
|
The |
|
The |
|
The |
|
The |
|
The |
|
A |
|
The |
|
This is a collection class that stores |
|
This is a collection class that stores |
|
This is a collection class that stores |
|
This is a collection class that stores |
Let's take a look at the skeleton code for the BaseAccount
class. As each sales force account is represented by a Datarow
in the dataset, you will need to pass in the data row to the constructor of the BaseAccount
class.
namespace CRMLive
{
public class BaseAccount
{
public enum AccountTypes
{
Lead,
Opportunity,
Customer
}
private TaskCollection _Tasks;
private HistoryCollection _Histories;
private FileCollection _Files;
private DataSet _MappedDataSet;
private DataRow _MappedDataRow;
private string _ValidationResult;
public DataRow MappedDatarow
{
get
{
return _MappedDataRow;
}
}
public DataSet MappedDataset
{
get
{
return _MappedDataSet;
}
}
public BaseAccount(DataRow MappedDatarow)
{
_MappedDataRow = MappedDatarow;
_MappedDataSet = _MappedDataRow.Table.DataSet;
_Tasks = new TaskCollection(this,
_MappedDataSet.Tables["AccountTasks"]);
_Histories = new HistoryCollection(this,
_MappedDataSet.Tables["AccountHistories"]);
_Files = new FileCollection(this,
_MappedDataSet.Tables["AccountFiles"]);
}
}
The BaseAccount
class would need to expose each column in the Datarow
as a property. The following code shows an example on how you can expose the FirstName
field as a property.
public string FirstName { get { if (Information.IsDBNull(_MappedDataRow["FirstName"]) == true) { return ""; } else { return _MappedDataRow["FirstName"].ToString(); } } set { _MappedDataRow["FirstName"] = value; } }
Let's take a look now at both the Task
class and TaskCollection
classes. A data row is also passed in to the constructor of the Task
class, which can then be exposed and accessed via properties in the same fashion.
public class Task
{
private DataRow _MappedRow;
public string TaskSubject
{
get
{
if (Information.IsDBNull(_MappedRow["TaskSubject"]) == true)
{
return "";
}
else
{
return _MappedRow["TaskSubject"].ToString();
}
}
set
{
_MappedRow["TaskSubject"] = value;
}
}
public DateTime TaskDate
{
get
{
if (Information.IsDBNull(_MappedRow["TaskDate"]))
{
return DateTime.Now;
}
else
{
return
System.Convert.ToDateTime
(_MappedRow["TaskDate"]);
}
}
set
{
_MappedRow["TaskDate"] = value;
}
}
.
.
.
public DataRow MappedRow
{
get
{
return _MappedRow;
}
}
public Task(DataRow DataRowItem)
{
_MappedRow = DataRowItem;
}
}
Collection classes will be created differently. Most of the collection classes that you create in this application will need to inherit from the System.Collections.Generic.List
class. The reason for this is that the .NET Datagrid only recognizes and binds to objects that implement either the IList
or IListView
interface. The DataSet
object, for example implements the IList
interface.
Most of the methods required of a collection object such as the Count()
and Contains()
method are already provided in the List
base class. You only need to create your own custom AddTask()
and RemoveTask()
functions. These custom functions do the additional step of adding the new data rows to the data table of the internally stored dataset. The following code shows how the TaskCollection
class can be created.
public class TaskCollection :
System.Collections.Generic.List<Task>
{
private DataTable _TaskDataTable;
private BaseAccount _parent;
public BaseAccount Parent
{
get
{
return _parent;
}
}
public Task AddTask(Task TaskItem)
{
base.Add(TaskItem);
_TaskDataTable.Rows.Add(TaskItem.MappedRow);
return TaskItem;
}
public void RemoveTask(Task TaskItem)
{
TaskItem.MappedRow.Delete();
base.Remove(TaskItem);
}
public TaskCollection(BaseAccount Parent, DataTable
TableItem)
{
int _counter;
Task _task;
_parent = Parent;
_TaskDataTable = TableItem;
for (_counter = 0; _counter <= _TaskDataTable.Rows.Count
- 1; _counter++)
{
_task = new Task(_TaskDataTable.Rows[_counter]);
base.Add(_task);
}
}
}
Adding a new task to a BaseAccount
object, for example, is a two-step process usually done in the following fashion. The first step creates the new task object and the second step adds it to the Tasks
collection.
Task _task = _myBaseAccount.NewTask();
.
.
.
_myBaseAccount.Tasks.AddTask(_task);
The NewTask()
method can be implemented in the BaseAccount
class using the following code snippet:
public Task NewTask() { DataRow _row = _MappedDataSet.Tables["AccountTasks"].NewRow(); Task _task = new Task(_row); return _task; }
Another important role of the logic tier is to validate its data before it reaches the data tier. You can provide validation functionality to the rest of the application by exposing a Validate()
function in the business object class. This function will run through all the necessary validations required and finally return true
or false
depending on the outcome of the validation. The following code shows how this function might look in the BaseAccount
class.
private string _ValidationResult; public string GetValidationResult() { return _ValidationResult; } public bool Validate() { _ValidationResult = ""; //We ensure that the Account GUID is not empty if (AccountGUID.ToString().Length == 0) { _ValidationResult = "Account GUID cannot be empty"; return false; } //We ensure that at least one phone number have been filled in if (ResPhoneNo.Trim().Length == 0 && MobPhoneNo.Trim().Length == 0) { _ValidationResult = "Please fill in at least one contact number"; return false; } //We ensure that both the first name and last name fields //have been filled in if (FirstName.Length == 0 || LastName.Length == 0) { _ValidationResult = "Please fill in both the first name and last name fields"; return false; } //Make sure that an account with the same name does not //already exist if (GlobalArea.PluginManager.GetActivePlugin.AccountExists (FirstName, LastName, this.AccountGUID) == true) { _ValidationResult = "An account with the same name already exists. Please use a different name"; return false; } return true; }
3.149.214.32