Save Objects to Database
- 6 minutes to read
Common business tasks require additional logic before you store changes in a database. For example, you may need to get objects that are about to be saved and write additional information to them or save objects in a custom way.
In a Controller
Members | Description |
---|---|
Methods: | |
Saves all the changes made to the persistent objects belonging to the current Object Space to the database. | |
Indicates whether the specified object has been added, deleted or modified, but not committed in the transaction currently in progress. | |
Returns a collection of persistent objects that will be saved when the current transaction is committed, including objects that will be saved in the parent transaction(s), optionally. | |
Removes the specified object from the list of objects to be committed. | |
Properties: | |
Indicates whether the Object Space is currently committing the changes made to its object(s). | |
Returns a collection of objects that have been created, modified or deleted after they were retrieved or committed. | |
Events: | |
Occurs before persistent objects are saved to the database. | |
Occurs after persistent object changes are stored in the database. | |
Replaces the default commit logic with a custom one. | |
Occurs before saving changes made to a specified persistent object to the database. | |
Occurs after saving changes made to a specified persistent object to the database. |
XAF saves changes in a View in the following situations:
- The Save, Save and New, or Save and Close Action is executed.
- In a popup window, the Accept Action is executed and the DialogController.SaveOnAccept property is
true
.
In XAF WinForms applications, you can control if XAF automatically saves or rolls back changes when you change the current object or close a View. The ModificationsController.ModificationsHandlingMode property specifies this behavior.
Persist Changes in Code
To save changes in your code, call the IObjectSpace.CommitChanges method.
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
public class MyViewController : ObjectViewController<DetailView, Department> {
public MyViewController() {
var action = new SimpleAction(this, "Change Title and Save changes", DevExpress.Persistent.Base.PredefinedCategory.Save);
action.Execute += Action_Execute;
}
private void Action_Execute(object sender, SimpleActionExecuteEventArgs e) {
Department department = (Department)View.CurrentObject;
department.Title = "New Title";
this.ObjectSpace.CommitChanges();
}
}
Obtain Modified Objects
To write additional information to an object before saving it to a database, do this in the IObjectSpace.Committing event handler. The following code illustrates this. An Employee
business object has the Notes
property that stores audit information on a number of other objects that were changed in the current transaction.
using DevExpress.ExpressApp;
using System.Collections;
public class MyViewController : ObjectViewController<DetailView, Employee> {
protected override void OnActivated() {
base.OnActivated();
ObjectSpace.Committing += ObjectSpace_Committing;
}
void ObjectSpace_Committing(object sender, System.ComponentModel.CancelEventArgs e) {
ICollection collection = ObjectSpace.GetObjectsToSave(false);
Employee owner = (Employee)View.CurrentObject;
owner.Notes = string.Format("{0};{1} objects changed", owner.Notes, collection.Count);
}
}
Process Saving Objects
It’s also possible to save changes in a custom way in the IObjectSpace.CustomCommitChanges event handler. To prevent the default logic to persist objects, handle the IObjectSpace.Committing event and call the IObjectSpace.RemoveFromModifiedObjects method to remove an object from being committed in the event handler.
The following code shows how to manually save child collection changes made in the parent Detail View:
using DevExpress.ExpressApp;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using YourSolutionName.Module.BusinessObjects;
namespace YourSolutionName.Module.Controllers {
public class MyViewController : ObjectViewController<DetailView, Parent> {
List<Child> detailObjectsCache = new List<Child>();
protected override void OnActivated() {
base.OnActivated();
ObjectSpace.CustomCommitChanges += ObjectSpace_CustomCommitChanges;
ObjectSpace.Committing += ObjectSpace_Committing;
ObjectSpace.Committed += ObjectSpace_Committed;
ObjectSpace.Reloaded += ObjectSpace_Reloaded;
}
protected override void OnDeactivated() {
ObjectSpace.CustomCommitChanges -= ObjectSpace_CustomCommitChanges;
ObjectSpace.Committing -= ObjectSpace_Committing;
ObjectSpace.Committed -= ObjectSpace_Committed;
ObjectSpace.Reloaded -= ObjectSpace_Reloaded;
base.OnDeactivated();
}
void ObjectSpace_Reloaded(object sender, EventArgs e) {
detailObjectsCache.Clear();
}
void ObjectSpace_Committed(object sender, EventArgs e) {
detailObjectsCache.Clear();
}
void ObjectSpace_Committing(object sender, CancelEventArgs e) {
IObjectSpace os = (IObjectSpace)sender;
for (int i = os.ModifiedObjects.Count - 1; i >= 0; i--) {
object item = os.ModifiedObjects[i];
if (typeof(Child).IsAssignableFrom(item.GetType())) {
detailObjectsCache.Add(item as Child);
os.RemoveFromModifiedObjects(item);
}
}
}
void ObjectSpace_CustomCommitChanges(object sender, HandledEventArgs e) {
// Implement custom logic to save Detail objects here.
foreach (Child detailObject in detailObjectsCache) {
//...
}
}
}
}
Note
We recommend that you handle these events only for root Views. Nested Views use their parent View’s Object Space. As a result, the events are handled several times. You can specify the controller scope as described at Define the Scope of Controllers and Actions. Alternatively, you can check properties like View.IsRoot and IObjectSpace.Owner in event handlers to determine for what View a handler is invoked.
In a Data Model (in an XPO Business Class)
In XPO business objects, override the OnSaving
method of a business class to perform additional logic when you save data. A common task is to generate an auto-incremented number or a sequence. Find a sample for how to implement this at XAF - How to generate a sequential number for a persistent object within a database transaction with Entity Framework Core.
Be aware of the following specifics when you implement custom logic with Integrated Mode or Middle Tier Security:
- The
OnSaving
andOnDeleting
methods of a business class can be called multiple times because Integrated Mode and Middle Tier Security use more than oneSession
/DbContext
object. If you implement custom logic in these methods, check whether a new value is already assigned to a property. This helps you avoid incorrect results. The following article describes how to do this with XPO: XPO Best Practices. Detail Views do not display changes made to an object within a transaction (for example, an auto-generated sequential number for an XPO business object) even if you saved this object in a View. These changes are made on the server only and are not automatically passed to the client application. To show these changes, reload the object. If you want to reload the object on each Save operation, override the business class’s
OnSaved
method. The following example demonstrates how to override this method to reload an object on the client:using DevExpress.Persistent.BaseImpl; // ... public class DemoObject : BaseObject { // ... protected override void OnSaved() { base.OnSaved(); // 0 is the default value if (Number == 0) { Session.Reload(this); } } }
The Session.DataLayer property is null in the secured
Session
. Instead ofDataLayer
, we recommend that you use the View.ObjectSpace property to query and change data from the database. This technique is also recommended for non-secure applications.If this recommendation does not apply to your scenario, use the Session.ObjectLayer property instead of
DataLayer
.You can also execute different code on the server and client depending on the
DataLayer
andObjectLayer
property values. The following example demonstrates how to do it:using DevExpress.ExpressApp.Security.ClientServer; using DevExpress.Persistent.BaseImpl; // ... public class DemoObject : BaseObject { // ... protected override void OnSaving() { if(Session.DataLayer != null && !(Session.ObjectLayer is SecuredSessionObjectLayer)) { // Server-side code } else { // Client-side code } base.OnSaving(); } }