Quick Objects Documentation Akal Tech Logo
Bulk Save and Disconnected Mode

Glossary Item Box

Business Logic Framework - Tutorial 21:

In this tutorial we will see how to implement a disconnect mode with a later save/sync capability.  The disconnected mode can be a very useful feature in case you are dealing with any of the following scenarios:

The disconnected mode is a powerful feature that allows you to make changes (inserts/updates/deletes) to multiple related objects and it can synchronize them later.  During synchronization/save process the all the relationships and keys are automatically handled simplifying the entire process.  In prior versions we had great support for building web services, however you were restricted to using the bare bones entities that were generated by the wsdl.  Although this capability remains the same, you now can leverage the full power of the business objects on the client by simply passing the data between the client and the web service.

This tutorial will cover the scenario where the user is given the capability to make changes to data locally and the user can press Save or Cancel buttons. When the user presses the Save button, the business object will then try to save the entire set of changes (including any made to related tables). If the user presses the Cancel button, all changes can be simply discarded without ever sending anything to the database.

 

1 // The purpose of this tutorial is to demonstrate the business object's capability to bind  
2 // to various controls and also be able to work in a disconnected mode where multiple changes are  
3 // saved together.  
4  
5 // We create a property called Customer that will hold an instance of Customers class.  
6 // This instance will be used to load the user specified customer object and also bind to   
7 // the text boxes on the screen.  
8 private Customers _customer = null;  
9 public Customers Customer  
10 {  
11     get 
12     {  
13         // We just want to ensure that if _customer object is null we create a new instance  
14         // and return it.  
15         if (_customer == null)  
16         {  
17             _customer = new Customers();  
18         }  
19         return _customer;  
20     }  
21     set 
22     {  
23         _customer = value;  
24     }  
25 }  
26  
27 public Tutorial21()  
28 {  
29     InitializeComponent();  
30 }  
31  
32 private void Button_LoadCustomer_Click(object sender, EventArgs e)  
33 {  
34     // Check if the user specified a value for Customer ID  
35     if (this.TextBox_CustomerID.Text.Trim().Length > 0)  
36     {  
37         // Here we simply parse the user specified value into the CustomerID field.  
38         // The value specified must be a valid integer other wise the Parse method will throw an exception.  
39         this.Customer.CustomerID.Parse(this.TextBox_CustomerID.Text);  
40           
41         // Create a join with Orders object.  
42         this.Customer.Join_CustomerID_Orders_Child();  
43  
44         // Load method expects that you specify the PrimaryKey value(s) before calling Load, and since we have already  
45         // specified the CustomerID value we can go ahead and call the Load method.  
46         // Since we have created a Join with a Child object, the Load method will automatically load all (orders) child records  
47         // for the specified customer.  
48         if (this.Customer.Load())  
49         {  
50             // Now that the Customer and Order data has been loaded, we can change the ObjectState to Disconnected.  
51             // Disconnected state allows the objects to retain all user's changes locally, and they are not sent to the database  
52             // unless the ObjectState changes or the Save method is called.  
53             // Note: This is new property introduced in version 3.x  
54             this.Customer.ObjectState = ObjectStates.Disconnected;  
55  
56  
57             // Setting the ObjectMode of the Customer to Save will ensure that anytime  
58             // a value for FirstName or LastName is changed in the user interface their UseInSave  
59             // property values are changed to true.  
60             this.Customer.ObjectMode = ObjectModes.Save;  
61               
62             // We dropped a BindingSource component to the form and set the binding between  
63             // TextBox_FirstName.Text and Customer.FirstName  
64             // TextBox_LastName.Text and Customer.LastName  
65  
66             // It is time to supply a value for the DataSource.  
67             this.customersBindingSource.DataSource = this.Customer;  
68               
69             // Since the Customer has been loaded we can enable the Save/Cancel buttons  
70             this.Button_Cancel.Enabled = true;  
71             this.Button_Save.Enabled = true;  
72  
73             // Disable the TextBox for entering CustomerID until the Cancel button is clicked.  
74             this.TextBox_CustomerID.Enabled = false;  
75  
76             // During the Load process for Customer if any orders were found they will be in a separate  
77             // table in the ResultSet property of the Customer object.  
78             // So we can simply assign the ResultSet property value to the OrderGrid's DataSource  
79             this.OrdersGrid.DataSource = this.Customer.ResultSet;  
80               
81             // Since, the ResultSet will contain more than one table, we need to set the DataMember  
82             // property of the OrdersGrid with the table name for Orders data.  
83             // This can be easily achieved by calling the GetResultSetName() method of the   
84             // Orders object instance that is joined to Customer. Calling GetResultSetName  
85             this.OrdersGrid.DataMember = this.Customer.CustomerID_Orders_Child.GetResultSetName();  
86               
87             // If there is at least one row in the Grid then we'll set the Last row's CustomerID cell's value to the current Customer's CustomerID value.  
88             if (this.OrdersGrid.Rows.Count > 0)  
89             {  
90                 this.OrdersGrid.Rows[this.OrdersGrid.Rows.Count - 1].Cells["CustomerID"].Value = this.Customer.CustomerID.Value;  
91             }  
92  
93             this.OrdersGrid.Enabled = true;  
94             this.Label_Message.Text = "Customer " + this.Customer.FirstName.Value + " " + this.Customer.LastName.Value + " loaded..";  
95         }  
96         else 
97         {  
98             this.Label_Message.Text = "Invalid Customer ID!";  
99             this.OrdersGrid.Enabled = false;  
100         }  
101     }  
102 }  
103  
104 private void Button_Cancel_Click(object sender, EventArgs e)  
105 {  
106     if (this.Customer != null)  
107     {  
108         this.Customer.Dispose();  
109         this.Customer = null;  
110     }  
111     this.Label_Message.Text = "";  
112     this.TextBox_CustomerID.Text = "";  
113     this.Button_Save.Enabled = false;  
114     this.Button_Cancel.Enabled = false;  
115     this.TextBox_FirstName.Text = "";  
116     this.TextBox_LastName.Text = "";  
117     this.OrdersGrid.DataSource = null;  
118     this.OrdersGrid.Enabled = false;  
119     this.TextBox_CustomerID.Enabled = true;  
120 }  
121  
122 private void Button_Save_Click(object sender, EventArgs e)  
123 {  
124     // Check to see if the user modified either the Orders data or the Customer information.  
125     // Customer's ResultSet property also contains the table that has all the order data.  
126     if (this.Customer.ResultSet.HasChanges() || this.Customer.IsDirty)  
127     {  
128         bool isvalid = false;  
129         // IsDirty flag on a business object can be used to determine if any of the property values have changed  
130         // since last time the object was loaded.  
131         // If the user did not make any changes, then there is no need to update such values.  
132         if (this.Customer.IsDirty)  
133         {  
134             // IsValid property returns true if all validators for all the fields are valid.  
135             // We have customized the "Customers" class, please refer to "CustomizedCustomers.cs" file  
136             // in the BusinessLogicLayer project to see that we have added RequiredFieldValidators   
137             // to FirstName and LastName fields.   
138             if (this.Customer.IsValid)  
139             {  
140                 // The current state of the Customer object is "Disconnected" and hence the Update method  
141                 // simply retain the changes in the ResultSet and will not be sending changes to the Database.  
142                 isvalid = this.Customer.Update();  
143             }  
144             else 
145             {  
146                 // ErrorString property of the business objects can be used to retreive each failed validator's error message.  
147                 // Error messages from the failed validators are separated by line breaks.  
148                 MessageBox.Show(this.Customer.ErrorString);  
149             }  
150         }  
151         // We are setting the ObjectMode to Save on the "CustomerID_Orders_Child" object of type "Orders"  
152         // This will ensure that when we call the Save method all the order records modified by the user will get saved.  
153         this.Customer.CustomerID_Orders_Child.ObjectMode = ObjectModes.Save;  
154           
155         // We want to ensure that either all changes are saved or nothing is saved so we can indicate to the business object  
156         // that it should run all the database commands in a transaction.  
157         this.Customer.UseTransaction = true;  
158  
159         // Starting version 3.x a new feature called Disconnected Mode was introduced.  
160         // When the ObjectState property is set to Disconnected all Insert/Update/Delete commands affect the local ResultSet only.  
161         // By calling the Save method these changes can be sent to the database.  
162         // In addition, even if you don't use the Disconnected mode, a DataSet can be easily synchronized by calling the Save method.  
163         // The save method will automatically save all changes, retreive (auto generated) primary key values as well as save all the "joined"  
164         // object's changes as well.  
165         if (this.Customer.Save())  
166         {  
167             this.Label_Message.Text = "All Changes Saved!";  
168         }  
169         else 
170         {  
171             this.Label_Message.Text = this.Customer.ErrorString;  
172         }  
173     }  
174  
175 }  
176  
177 private void OrdersGrid_RowLeave(object sender, DataGridViewCellEventArgs e)  
178 {  
179     // The following code will ensure that the current CustomerID value is always populated into any new rows that are created.  
180     // This is basically how we can ensure that every now row will automatically receive the current CustomerID value automatically.  
181     if (this.OrdersGrid.Columns.Contains("CustomerID")  
182         &&  
183         (this.OrdersGrid.Rows[e.RowIndex].Cells["CustomerID"].Value == null 
184         ||  
185         (String.IsNullOrEmpty(this.OrdersGrid.Rows[e.RowIndex].Cells["CustomerID"].Value.ToString()))  
186         )  
187         )  
188     {  
189         this.OrdersGrid.Rows[e.RowIndex].Cells["CustomerID"].Value = this.Customer.CustomerID.Value;  
190     }  
191 }  
192  
1 ' The purpose of this tutorial is to demonstrate the business object's capability to bind  
2 ' to various controls and also be able to work in a disconnected mode where multiple changes are  
3 ' saved together.  
4  
5 ' We create a property called Customer that will hold an instance of Customers class.  
6 ' This instance will be used to load the user specified customer object and also bind to  
7 ' the text boxes on the screen.  
8 Private _customer As Customers = Nothing 
9 Public Property Customer() As Customers  
10     Get 
11         ' We just want to ensure that if _customer object is null we create a new instance  
12         ' and return it.  
13         If _customer Is Nothing Then 
14             _customer = New Customers()  
15         End If 
16         Return _customer  
17     End Get 
18     Set(ByVal value As Customers)  
19         _customer = value  
20     End Set 
21 End Property 
22  
23 Public Sub New()  
24     InitializeComponent()  
25 End Sub 
26  
27 Private Sub Button_LoadCustomer_Click(ByVal sender As ObjectByVal e As EventArgs) Handles Button_LoadCustomer.Click  
28     ' Check if the user specified a value for Customer ID  
29     If Me.TextBox_CustomerID.Text.Trim().Length > 0 Then 
30         ' Here we simply parse the user specified value into the CustomerID field.  
31         ' The value specified must be a valid integer other wise the Parse method will throw an exception.  
32         Me.Customer.CustomerID.Parse(Me.TextBox_CustomerID.Text)  
33  
34         ' Create a join with Orders object.  
35         Me.Customer.Join_CustomerID_Orders_Child()  
36  
37         ' Load method expects that you specify the PrimaryKey value(s) before calling Load, and since we have already  
38         ' specified the CustomerID value we can go ahead and call the Load method.  
39         ' Since we have created a Join with a Child object, the Load method will automatically load all (orders) child records  
40         ' for the specified customer.  
41         If Me.Customer.Load() Then 
42             ' Now that the Customer and Order data has been loaded, we can change the ObjectState to Disconnected.  
43             ' Disconnected state allows the objects to retain all user's changes locally, and they are not sent to the database  
44             ' unless the ObjectState changes or the Save method is called.  
45             Me.Customer.ObjectState = ObjectStates.Disconnected  
46  
47             ' We dropped a BindingSource component to the form and set the binding between  
48             ' TextBox_FirstName.Text and Customer.FirstName  
49             ' TextBox_LastName.Text and Customer.LastName  
50  
51             ' It is time to supply a value for the DataSource.  
52             Me.CustomersBindingSource.DataSource = Me.Customer  
53  
54             ' Setting the ObjectMode of the Customer to Save will ensure that anytime  
55             ' a value for FirstName or LastName is changed in the user interface their UseInSave  
56             ' property values are changed to true.  
57             Me.Customer.ObjectMode = ObjectModes.Save  
58  
59             ' Since the Customer has been loaded we can enable the Save/Cancel buttons  
60             Me.Button_Cancel.Enabled = True 
61             Me.Button_Save.Enabled = True 
62  
63             ' Disable the TextBox for entering CustomerID until the Cancel button is clicked.  
64             Me.TextBox_CustomerID.Enabled = False 
65  
66             ' During the Load process for Customer if any orders were found they will be in a separate  
67             ' table in the ResultSet property of the Customer object.  
68             ' So we can simply assign the ResultSet property value to the OrderGrid's DataSource  
69             Me.OrdersGrid.DataSource = Me.Customer.ResultSet  
70  
71             ' Since, the ResultSet will contain more than one table, we need to set the DataMember  
72             ' property of the OrdersGrid with the table name for Orders data.  
73             ' This can be easily achieved by calling the GetResultSetName() method of the  
74             ' Orders object instance that is joined to Customer. Calling GetResultSetName  
75             Me.OrdersGrid.DataMember = Me.Customer.CustomerID_Orders_Child.GetResultSetName()  
76  
77             ' If there is at least one row in the Grid then we'll set the Last row's CustomerID cell's value to the current Customer's CustomerID value.  
78             If Me.OrdersGrid.Rows.Count > 0 Then 
79                 Me.OrdersGrid.Rows(Me.OrdersGrid.Rows.Count - 1).Cells("CustomerID").Value = Me.Customer.CustomerID.Value  
80             End If 
81  
82             Me.OrdersGrid.Enabled = True 
83             Me.toolStripLabel1.Text = "Customer " + Me.Customer.FirstName.Value + " " + Me.Customer.LastName.Value + " loaded.." 
84         Else 
85             Me.toolStripLabel1.Text = "Invalid Customer ID!" 
86             Me.OrdersGrid.Enabled = False 
87         End If 
88     End If 
89 End Sub 
90  
91 Private Sub Button_Cancel_Click(ByVal sender As ObjectByVal e As EventArgs) Handles Button_Cancel.Click  
92     If Me.Customer IsNot Nothing Then 
93         Me.Customer.Dispose()  
94         ' Somehow VB.NET does not unbind even after a CustomersBindingSource has been set to Nothing, so setting the fields to Nothing removes the binding.  
95         ' Note: This behavior is different from C#, in C# you don't need to set the fields to null as setting the CustomersBindingSource to null is sufficient.  
96         Me.Customer.FirstName = Nothing 
97         Me.Customer.LastName = Nothing 
98         Me.Customer = Nothing 
99     End If 
100     Me.toolStripLabel1.Text = "" 
101     Me.TextBox_CustomerID.Text = "" 
102     Me.Button_Save.Enabled = False 
103     Me.Button_Cancel.Enabled = False 
104     Me.TextBox_FirstName.Text = "" 
105     Me.TextBox_LastName.Text = "" 
106     Me.OrdersGrid.DataSource = Nothing 
107     Me.OrdersGrid.Enabled = False 
108     Me.TextBox_CustomerID.Enabled = True 
109 End Sub 
110  
111 Private Sub Button_Save_Click(ByVal sender As ObjectByVal e As EventArgs) Handles Button_Save.Click  
112     'Forces current control to perform databinding as if it lost focus.  
113     'Without the following line WinForms's databinding won't take affect until the TextBox loses focus.  
114     Me.Validate()  
115     ' Check to see if the user modified either the Orders data or the Customer information.  
116     ' Customer's ResultSet property also contains the table that has all the order data.  
117     If (Me.Customer.ResultSet.HasChanges() Or Me.Customer.IsDirty) Then 
118         Dim isvalid As Boolean = False 
119         ' IsDirty flag on a business object can be used to determine if any of the property values have changed  
120         ' since last time the object was loaded.  
121         ' If the user did not make any changes, then there is no need to update such values.  
122         If (Me.Customer.IsDirty) Then 
123             ' IsValid property returns true if all validators for all the fields are valid.  
124             ' We have customized the "Customers" class, please refer to "CustomizedCustomers.cs" file  
125             ' in the BusinessLogicLayer project to see that we have added RequiredFieldValidators   
126             ' to FirstName and LastName fields.   
127             If (Me.Customer.IsValid) Then 
128                 ' The current state of the Customer object is "Disconnected" and hence the Update method  
129                 ' simply retain the changes in the ResultSet and will not be sending changes to the Database.  
130                 isvalid = Me.Customer.Update()  
131             Else 
132                 ' ErrorString property of the business objects can be used to retreive each failed validator's error message.  
133                 ' Error messages from the failed validators are separated by line breaks.  
134                 MessageBox.Show(Me.Customer.ErrorString)  
135             End If 
136         End If 
137         ' We are setting the ObjectMode to Save on the "CustomerID_Orders_Child" object of type "Orders"  
138         ' This will ensure that when we call the Save method all the order records modified by the user will get saved.  
139         Me.Customer.CustomerID_Orders_Child.ObjectMode = ObjectModes.Save  
140  
141         ' We want to ensure that either all changes are saved or nothing is saved so we can indicate to the business object  
142         ' that it should run all the database commands in a transaction.  
143         Me.Customer.UseTransaction = True 
144  
145         ' Starting version 3.x a new feature called Disconnected Mode was introduced.  
146         ' When the ObjectState property is set to Disconnected all Insert/Update/Delete commands affect the local ResultSet only.  
147         ' By calling the Save method these changes can be sent to the database.  
148         ' In addition, even if you don't use the Disconnected mode, a DataSet can be easily synchronized by calling the Save method.  
149         ' The save method will automatically save all changes, retreive (auto generated) primary key values as well as save all the "joined"  
150         ' object's changes as well.  
151         If (Me.Customer.Save()) Then 
152             Me.toolStripLabel1.Text = "All Changes Saved!" 
153         Else 
154             Me.toolStripLabel1.Text = Me.Customer.ErrorString  
155         End If 
156     End If 
157 End Sub 
158  
159 Private Sub OrdersGrid_RowLeave(ByVal sender As ObjectByVal e As DataGridViewCellEventArgs) Handles OrdersGrid.RowLeave  
160     ' The following code will ensure that the current CustomerID value is always populated into any new rows that are created.  
161     ' This is basically how we can ensure that every now row will automatically receive the current CustomerID value automatically.  
162     If Me.OrdersGrid.Columns.Contains("CustomerID"AndAlso (Me.OrdersGrid.Rows(e.RowIndex).Cells("CustomerID").Value Is Nothing OrElse ([String].IsNullOrEmpty(Me.OrdersGrid.Rows(e.RowIndex).Cells("CustomerID").Value.ToString()))) Then 
163         Me.OrdersGrid.Rows(e.RowIndex).Cells("CustomerID").Value = Me.Customer.CustomerID.Value  
164     End If 
165 End Sub 
166  

The Customers class has been customized by specifying validation criteria on various fields.

1 public partial class Customers : Customers_Base  
2 {  
3  
4     // ****************************************  
5     // NOTE: We are using a feature called "partial classes" only available in from .NET 2.0 onwards  
6     // for this sample. If you would like to implement this in .NET 1.1 you can use the generated  
7     // Customers class under the "Custom" folder. In that case so it would be important for you to   
8     // NOT regenerate the "Custom" classes, or your changes will get overwritten.   
9     // Normally it is not necessary to regenerate "Custom" classes and it is only necessary add/remove   
10     // tables from the underlying schema. If you have customized code, please make sure to create a backup  
11     // before regenerating.  
12     // ****************************************  
13  
14     // Override the OnObjectInitialized method and it can be used  
15     // to further customize your business object after the object has been initialized.  
16     // For example: Below we will add validation  
17     // to the FirstName and LastName fields.   
18     protected override void OnObjectInitialized(ObjectEventArgs e)  
19     {  
20         // Make sure to call the base OnObjectInitialized method else the ObjectInitialized event handlers will not be fired.  
21         base.OnObjectInitialized(e);  
22  
23         // Starting v3.x you are able to leverage the power of built in validation and  
24         // the following just shows a simple view of it.  
25  
26         // We are going to add a RequiredFieldValidator. This field validator  
27         // is not dependent on the IsNull property of the field, and hence you can  
28         // mark any field as required even if the field in the database allows nulls.  
29         // There are multiple overloads available for each validator, lets review two of them.  
30         RequiredFieldValidator fnValidator = new RequiredFieldValidator();  
31  
32         // We must specify which field will be validated by this Validator.  
33         fnValidator.FieldToValidate = this.FirstName;  
34         // Now we need to add the validator to the Field's Validators collection.  
35         // Note: It is possible to apply a validator to one field while it is going to validate the value  
36         // of a different field. This is by design to ensure utmost control and flexibility over validation process.  
37         this.FirstName.Validators.Add(fnValidator);  
38  
39         // The following overload can used to directly set the FirstToValidate property as well  
40         // as add the validator into the field's Validators collection.  
41         new RequiredFieldValidator(this.LastName, true);  
42     }  
43 }  
44  
1 Partial Public Class Customers  
2     Inherits Customers_Base  
3  
4     ' ****************************************  
5     ' NOTE: We are using a feature called "partial classes" only available in from .NET 2.0 onwards  
6     ' for this sample. If you would like to implement this in .NET 1.1 you can use the generated  
7     ' Customers class under the "Custom" folder. In that case so it would be important for you to   
8     ' NOT regenerate the "Custom" classes, or your changes will get overwritten.   
9     ' Normally it is not necessary to regenerate "Custom" classes and it is only necessary add/remove   
10     ' tables from the underlying schema. If you have customized code, please make sure to create a backup  
11     ' before regenerating.  
12     ' ****************************************  
13  
14     ' Override the OnObjectInitialized method and it can be used  
15     ' to further customize your business object after the object has been initialized.  
16     ' For example: Below we will add validation  
17     ' to the FirstName and LastName fields.   
18     Protected Sub OnObjectInitialized(ByVal e As ObjectEventArgs)  
19  
20  
21         ' Make sure to call the base OnObjectInitialized method else the ObjectInitialized event handlers will not be fired.  
22         MyBase.OnObjectInitialized(e)  
23  
24         ' Starting v3.x you are able to leverage the power of built in validation and  
25         ' the following just shows a simple view of it.  
26  
27         ' We are going to add a RequiredFieldValidator. This field validator  
28         ' is not dependent on the IsNull property of the field, and hence you can  
29         ' mark any field as required even if the field in the database allows nulls.  
30         ' There are multiple overloads available for each validator, lets review two of them.  
31         Dim fnValidator As RequiredFieldValidator = New RequiredFieldValidator()  
32  
33         ' We must specify which field will be validated by this Validator.  
34         fnValidator.FieldToValidate = Me.FirstName  
35         ' Now we need to add the validator to the Field's Validators collection.  
36         ' Note: It is possible to apply a validator to o