Advanced programming


The following class diagram is used for explanation:

 


"gip.core.datamodel. Database" is the database model for iPlus. It contains tables / entities for the iPlus runtime system, rights management and development.

"gip.mes.datamodel. DatabaseApp" is the database model for Varobatch.MES. It contains tables / entities for the MES product range.

Both are derivatives of "System.Data.Objects.ObjectContext" (Entity Framework 4) and implement the interface "gip.core.datamodel.IACEntityObjectContext". IACEntityObjectContext is used to abstract common database functions that are required for the iPlus framework (rights management, logging of database errors, initialization of new entity objects with default values, validation of field values ​​and lengths ...). IACEntityObjectContext also reflects a large part of the methods defined in "System.Data.Objects.ObjectContext". If you want to develop your own database model and integrate it into iPlus, you have to implement this interface. The shared logic is implemented in the helper class "gip.core.datamodel.ACObjectContextHelper", which has every context as a member and to which all method calls are delegated.

 

--V5 Version--

In iPlusV5, both database models share a common inheritance from "Microsoft.EntityFrameworkCore.DbContext" The core functionalities remain unchanged, with only a few modifications to consider while creating your models. One of the changes is that you cannot directly bind a store query to WPF controls like ListView, DataGrid, ListBox. Doing so will result in an exception with the message: "Data binding directly to a store query (DbSet, DbQuery, DbSqlQuery) is not supported."

To address this, a recommended approach is to declare a List variable for each Entity object. Then, within the Database class, apply the ToArray() method on each of these objects.


A started iPlus application (Windows service or desktop application) instantiates four long-term database contexts (see the four numbers above in the picture).

(1) A global instance of "gip.core.datamodel.Database", that can be accessed via the static property static Database GlobalDatabase. It is only required for the iPlus type system. Avoid using this context for queries! Each query must be secured with a thread lock:static Database GlobalDatabaseusing (ACMonitor.Lock(QueryLock_1X000)) {...} 

(2) Another instance of gip.core.datamodel.Database, which is used for reading and writing of

(3) A global database instance is generated for server-side application projects. This can either be an instance of Database, DatabaseApp or another database context that implements  IACEntityObjectContext. This is defined via the configuration property "gip.core.autocomponent.ACRoot.TypeNameOfAppContext", in which the AssemblyQualifiedName must be entered. This instance can be accessed via the propertystatic RootDbOpQueue.AppContextQueue.Context

(4)  Shared database context for business objects that need DatabaseApp as context. This context is generated dynamically as soon as it is needed and also closed again when all business objects have been closed. The ACObjectContextManager does this:

The ACObjectContextManager is a static class that is responsible for managing long-term contexts. The four contexts mentioned above use this class. Using the various overloaded "GetOrCreateContext()" method, the ACObjectContextManager uses a key name to find out whether a long-term context already exists and returns it as a return value. If there is no context in its list, it generates an appropriate instance. Using DisposeAndRemove(), he removes all objects from the ObjectStateManager (change tracking) and removes the context from his list.


The database contexts (2) and (3) are generated indirectly by instantiating a database queue. Database queues are classes of the generic type "ACEntityOpQueue<T>", which are derived from ACDelegateQueue. Delegate queues consist of a queue of the type "Queue<Action>"  and an internal thread that processes this queue as soon as new entries are available. The entries are ".NET delegates ". The generic type of a database queue must be of type IACEntityObjectContext. Database queues enables a thread-safe usage of database contexts by placing all LINQ operations in the queue (using the method void Add(Action action)) and, after processing the queue, using the "IACEntityObjectContext.ACSaveChanges()" method to save them automatically. The advantage of this is that the application thread is not blocked with tasks that can run asynchronously. However, if you want to perform a LINQ operation immediately (synchronously), you can do this using the method void ProcessAction(Action action)


"gip.core.datamodel.Database"  is the database model for iPlus. It contains tables / entities for the iPlus runtime system, rights management and development.

Tables:


"gip.mes.datamodel.DatabaseApp"  is the database model for Varobatch.MES. It contains tables / entities for the MES product range.

Tables:


Pre-compiled queries

Pre-compiled queries are faster because the expression tree only has to be created once by the compiler. If you only want to have read access to the data, then prefer "new" in the select statement instead of materializing entire entity objects. 

 

--V5 Version--

In the iPlus V5 version the command for using precompiled queries has changed. Instead of using CompiledQuery.Compile you use EF.CompileQuery. It also has some new limitations and in this case the result can't be an IQueryable type as the compiled query delegate cannot be composed further. You also cannot use the entities directly in the compiled query as that cannot be translated. There are also some other limitations as there are some LINQ methods that also cannot be translated.

 

Add records

You create new entity objects with the static method "NewACObject()", which every entity class should provide in the iPlus framework. This initializes all properties and assigns them with standard values ​​that have been set in the development environment. Then call the AddObject() method to announce the new object to the change tracking. To save, call ACSaveChanges() instead of the usual SaveChanges() method. ACSaveChanges() validates all changed objects against the set min / max values. If the validation or the subsequent saving process has failed, you will receive a Msg object with error information and severity. With ACUndoChanges() you can undo your changes. This is necessary in long-term contexts,

 

--V5 Version--

In Entity Framework Core, the method for adding objects has been simplified. Instead of using AddObject(), you can now directly use Add(). The functionality of adding objects remains consistent.

 

Optimized queries and deletion

Use the Include() extension method to load other related entity objects with a query. In the following example, the entity framework does not have to perform lazy loading as soon as the MDMaterialGroup property is accessed:

A delete operation is carried out with DeleteACObject(). The second parameter specifies whether validations should be carried out on the client side (e.g. due to referential integrity). The third parameter specifies whether the data record should be physically deleted in the database or whether a deletion indicator is only set instead. In the example, an attempt is first made to physically delete and, in the event of an error, a soft delete.

 

--V5 Version--

A breaking change in Entity Framework Core and the iPlusV5 framework is that lazy loading has a different behavior and in the example provided where the property mat.MDMaterialGroup.MDMaterialGroupName is accesssed without lazy loading can't be replicated in the new framework and the property will be lazy loaded because of the new behavior.

 

Queries on the global database context (1)

Use this context only if you need information from the type system (information about classes, properties, methods, XAMLDesigns ...). Please note that long-term contexts must always be queried thread-safe, because you can never know whether another query is being carried out in another thread at the same time. Use the ACMonitor class for this and pass the lock object "QueryLock_1X000", which, because of the IACEntityObjectContext interface, must provide every EF context class:

 

Basically, please prefer "using() with its own context" because this avoids the thread problems and the memory will then be released again.

 

Working with database queues (2), (3)

Any queries and changes to entity objects for database contexts belonging to a queue must be built into an anonymous method and passed as a delegate to the Add() or ProcessAction() method:

Because the Add() and ProcessAction() methods internally lock threads using QueryLock_1X000, you, as an application programmer, no longer have to do this.

 

Long-term database contexts

If a long-term context is required for technical reasons, then instantiate the context using the ACObjectContextManager. Keep the reference to the new context in a private field and return it in the overridden property "public override IACEntityObjectContext Database". All child instances that access your Database property get this context back, because the context of the ParentACComponent is always returned in the basic implementation. If no class overwrites the Database property, you get the database context of the root instance. In an application, it is always the ApplicationManager (class "gip.core.autocomponent.ApplicationManager"), the "RootDbOpQueue.AppContext (3)" returns. If the long-term context is no longer required, remove it from the ACObjectContextManager using "DisposeAndRemove()".