Advanced programming


Business objects are classes that are instantiated on demand (dynamically). Typically, they are a child-instances of "Root\Businessobjects". They can also be child instances of any ACComponent or defined as a static instance in the application tree.

At a dynamic instantiation its ACIdentifier is extended with a  sequential instance number. For instance, if there is a business object of the class "BSOMaterial", then the first instance has the ID 1 and thus the ACIdentifier "BSOMaterial(1)". If the user opens a second tab item, the ACIdentifier is "BSOMaterial(2)" etc.

Due to the dynamic instantiation, business objects shouldn't have persistable properties. Therefore the ACClassInfo-Attribute class must be declared with "Global.ACStorableTypes.NotStorable".

Business objects are build according to the Model in the MVVM design pattern and are accessible via the BSOACComponent property in  VBDesign's in the View model. Typically, the "FrameworkElement.DataContext" property also refers to a business object.

The primary purpose of business objects is to handle data from application tables. In concrete terms, this means creating, deleting and modifying data

Basically, there are two types of business objects: 

 

 

Navigable means, that a business object has a ACAccessNav<T> property to be able to navigate, search an save in the ribbon-bar:

 

Navigationless means, that the business object does not work with database tables. In the ribbon bar, the previously framed icons are omitted:

 

Business objects are started either via the Menu or Favorites Bar.


A business object needs a database context to be able to work with it. Therefore the base class ACBSO provides the following virtual property:

public virtual IACEntityObjectContext Database { get; }

This property can be overwritten in a derived business object and a separate instance of a database context can be returned. Database contexts in business objects are thus so-called "Long-term contexts". For the  lifecycle of the database context the implementing business object responsible. This means that that a database context in the overwritten ACDeInit() method should be closed again.

Separate context

To instantiate own database context for each bussiness object, however, also has disadvantages:

  1. High memory consumption.
  2. Different entity object instances for the same record. If you have opened one business object for managing material master data and another for managing bill of materials, then the same material that they are in one business object, is not automatically updated in the GUI, because they are different objects from two different contexts.

Shared context

Using a common context over many business objects eliminates these disadvantages. On the other hand, sharing database contexts has a different side effect: calling SaveChanges in a business object, means that any changes made in the other business objects are also saved..

Separate or together?

This decision is up to you and cannot be answered in general. It essentially depends on the functions of the business object. You can implement both variants and should inform the end user about the behavior of your business object. In any case, dealing with common contexts requires intelligent context management, which also closes the common context when closing the last business object. Conversely, the same context should be reused when opening further business objects. That's why don't you have to worry abou, because business objects have implicitly already built this in.


Basically, you already have learned the context management in the database chapter. There is also explained the use of Long-term contexts via ACObjectContextManager in the program code . You should also use the ACObjectContextManager for business objects as implemented in the sample project in the class BSOMyCompNav:

 

The sample class BSOMyCompNav is a base class for all other business objects that should use the same database context. You can build your own base class in this way, which is inherited from ACBSO if you only want to program navigationless business objects.

 

Automatic instantiation

Using the shared context is implemented in the GetAppContextForBSO(ACComponent bso) method by calling the generic GetOrCreateContext method on the ACObjectContextManager and passing the "BSOMyCompContext" key. If an instance already exists under this key, the already existing is returned. Thus, each new business object gets the same context when the virtual database property is accessed. DatabaseApp is only used for the derived classes, so that you do not cast to the concrete entity object database class when LINQ queries must be programmed.

 

Automatic closing

You'll wonder, where is the logic that a database context is closed when all business objects have been terminated? 

This is already implemented in ACBSO. Each time a business object is closed, the ACDeInit method checks whether there are any other business objects that use the same context. If not, the last business object calls the virtual method OnDisposeDatabase() that instructs the ACObjectContextManager to close the database context. If you override this method, don't forget to make the base call. Otherwise, the context remains open and all objects remain in the change tracker.

 

Fresh data

After the last business object was closed, the old context disappears and the memory is freed. Now, when a new business object is opened again, a new context is generated and all data is reloaded from the database! This avoids update problems if your business object is not programmed to load all related data with RefreshMode.StoresWins.


Method call order when saving

The following virtual methods are defined in ACBSO and related to the saving process: 

protected virtual bool OnSave()
protected virtual Msg OnPreSave()
protected virtual void OnPostSave()

You can overwrite these to influence the saving process in your BSO. Don't forget the base call when overwriting!

The saving process is initiated with the OnSave() method. The OnSave () method then makes a series of method calls:

  1. Aufruf von protected MsgWithDetails NotifyAllPreSave():
    "NotifyAllPreSave ()" iterates over all business objects  that have the same data context and then calls the OnPreSave () method. If this method has been overwritten in a derivation and a Msg instance is returned instead of a null value, the saving process is aborted and a dialog box opens with the error messages. Otherwise, continue with step 2:
  2. Call of  public bool ACComponent.ACSaveChanges():
    This method is defined in ACComponent. It calls the ACSaveChanges method on the "IACEntityObjectContext Database" virtual property. If the save process was unsuccessful, a dialog box opens and the error messages are displayed. Otherwise, continue with step 3:
  3. Call to protected void NotifyAllPostSave():
    "NotifyAllPostSave ()" iterates over all business objects  that have the same data context and then calls the OnPostSave () method to inform all business objects of the successful save

 

Method call order when undoing

In ACBSO, the following virtual methods are defined that are related to the undo: 

protected virtual bool OnPreUndoSave()
protected virtual Msg OnPreUndoSave()
protected virtual void OnPostUndoSave()

You can overwrite this in order to influence the reversal in your BSO. Don't forget the base call when overwriting !

The undo action is initiated with the OnUndoSave () method . The OnUndoSave () method then makes a series of method calls:

  1. Call to protected MsgWithDetails NotifyAllPreUndoSave():
    "NotifyAllPreUndoSave ()" iterates over all business objects  that have the same data context and then calls the OnPreUndoSave () method. If this method has been overwritten in a derivation and a Msg instance is returned instead of a null value, the undo process is canceled and a dialog box opens with the error messages. Otherwise, continue with step 2:
  2. Call of public bool ACComponent.ACUndoChanges():
    This method is defined in ACComponent. It calls the ACUndoChanges method on the "IACEntityObjectContext Database" virtual property. If the undo process was unsuccessful, a dialog box opens and the error messages are displayed. Otherwise, continue with step 3:
  3. Call to protected vois NotifyAllPostUndoSave():
    "NotifyAllPostUndoSave ()" iterates over all business objects  that have the same data context and then calls the OnPostUndoSave () method to inform all business objects of the successful rollback .

 

Preliminary check 

In the chapter on synchronous methods you got to know the principle of the IsEnabled methods . IsEnabled methods are used to deactivate UI buttons via CommandBinding . This technique can also be used to deactivate the save and undo buttons in the menu ribbon. This testing logic should be done by overriding the virtual methods

protected virtual bool OnIsEnabledSave()
protected virtual bool OnIsEnabledUndoSave()

implemented. Don't forget the base call when overwriting !

By calling the method and  you can query all business objects that work with the same database context at once. During iteration, the OnIsEnabledSave () or OnIsEnabledUndoSave () method is called on each business object. If the return value is incorrect, then a business object does not allow saving or undoing.protected bool NotifyAllIsEnabledSave()protected bool NotifyAllIsEnabledUndoSave()

 

Force user query

If a database context has been changed and you want the user to be prompted to confirm whether he wants to accept or undo the changes, call the method  public virtual bool ACSaveOrUndoChanges(). The return value of the method is true if the user has confirmed the save or the undo.


Now the question arises, how is the storage process actually initiated from the surface?

You learned in the design chapter that when you drag a method onto the design surface, a button (VBButton) is automatically generated and the VBContent is set with the method name. However, this only works if the method has been made known to the iPlus type system via ACMethodInfo . In any case, the OnSave methods are - for good reason - not provided with it.

You must therefore explicitly declare this storage method in your business object (or the derivatives of ACBSO in one of your own base classes). You then call the OnSave () method within this method.

Since most business objects always have the same basic functions, all business objects are displayed with the control element " gip.core.layoutengine. VBRibbon " by default. The menu ribbon (VBRibbon) itself has some buttons that are already provided with a predefined VBContent so that they are automatically linked to the methods of the business object . If a method is not defined in its business object, the corresponding button in the menu ribbon is also hidden.

 

Save errors in the ribbon
Iconmethod

This button is used to save. The VBContent is "! Save" . As a result, a method called "Save ()" must be declared in the business object:

[ACMethodCommand(InOrder.ClassName, "en{'Save'}de{'Speichern'}", 
(short)MISort.Save, false, Global.ACKinds.MSMethodPrePost)]

public void Save()
{
OnSave();
}
public bool IsEnabledSave()
{
return OnIsEnabledSave();
}

Note: The code is taken from the sample project. For your own business object, you have to adapt the ACMethodCommand declaration and the internal logic according to your use case. This also applies to the following code examples in the subsections.

Don't forget to define an IsEnabled method so that the button is deactivated in the ribbon. In the above example, OnIsEnabledSave () is called to check whether the database context has been changed and whether it needs to be saved.

This button is used to discard changes. The VBContent is "! UndoSave" .

[ACMethodCommand(InOrder.ClassName, "en{'Undo'}de{'Nicht speichern'}", 
(short)MISort.UndoSave, false, Global.ACKinds.MSMethodPrePost)]

public void UndoSave()
{
OnUndoSave();
}
public bool IsEnabledUndoSave()
{
return OnIsEnabledUndoSave();
}