Tuesday, 3 October 2017

DACs Inheritance and Caches

Hi All

Want to speak today about DACs inheritance in Acumatica and some issues that it cause to development.

As you know C#/.NET as OOP-oriented framework support inheritance of objects, but when in comes SQL and Database structure than there is no inheritance between tables.
As a bonus I will explain why we need BAccount and BAccount2 DACs and what is the difference.

Inheritance
In Acumatica we want to leverage benefits of OOP to simplify references between objects, minimize database size required, ease of reporting and so on.
As a result of that we have designed system in such way to support it:
You can have parent DAC
public class BAccount : IBqlTable { ... }
public class BAccount : IBqlTable { ... }
In the same time you may have child DACs
public class Vendor : BAccount { ... }

public class Customer : BAccount { ... }

In the database BAccount, Customer and Vendor are separate tables, but when you select Customer using PXSelect<Customer, Where<...>>, Acumatica will select both table from database using sub-query:
SELECT * from 
    (SELECT c.*, b.* FROM Customer c 
        INNER JOIN BAccount b on c.Key = b.Key)
WHERE ...

In the same time you can use BAccount separately - PXSelect<BAccount>.
Note that when you using Customer and changing fields from BAccount and Customer table together than Acumatica platform will handle database updates automatically.

For developers it gives a lot of benefits:
  • All OOP benifits
  • Usage simplifications
  • Less code to write
  • Update of several table at once

Cache Types Problem
Yes there are a lot of benefits, but as most of abstractions, they have some limitations.
You might seen the result such limitations already when you use BAccount, BAccount2, BAccountR data access classes.

This problem is related to DAC inheritance. When you use PXSelect<Customer> you actually use 2 DACs together (BAccount and Customer), but when you use PXSelect<BAccount> you use only one DAC - BAccount. Beside fact that you should be able to use these 2 DACs together and separately there are multiple custom attributes that are linked to BAccount or Customer types (static references). For example in BAccount DAC you may have PXSelector(typeof(BAccount.AcctCD))
In the same time in Customer DAC you may have selector PXDefault(typeof(BAccount.ClassID)) or PXEnabled(typeof(Where<Customer.creditLimit, Greater<decimal0>>))

As you may know from T200 training guide attributes finding caches by DAC types. For example, if code needs to find default value from BAccount PXDefault(typeof(BAccount.ClassID)), than it will access graph.Caches[typeof(BAccount)]. That means that even if you have PXSelect<Customer> you need to provide cache for both types and BAccount and Customer instantiated.

Lets have an example:
If you define data view PXSelect<Vendor> Acumatica platform will do following:
  1. Create cache for Vendor - PXCache<Vendor>.
  2. Put PXCache<Vendor> into Graph.Caches collection by type Vendor: graph.Caches[typeof(Vendor)] = new PXCache<Vendor>();
  3. Put PXCache<Vendor> into Graph.Caches collection by type BAccount: graph.Caches[typeof(Baccount)] = graph.Caches[typeof(Vendor)];
So we have graph.Caches[typeof(BAccount)] == graph.Caches[typeof(Vendor)] == PXCache<Vendor>.Cache;

Why we putting same cache by two types? There are few reasons:
  1. The main reason is polymorphism - Vendor DAC might customize some properties of BAccount DAC, so when you have same cache, you ensure that you use customized attributes.
  2. Performance/Memory optimizations - having one cache is cheaper that 2.
Ok, now we understand how that works, but there is the problem?
Problem is if you need to access exactly BAccount cache, than graph.Caches[typeof(Baccount)] will return you Vendor cache and not BAccount.
Example:
public class EmployeeMaint : PXGraph<EmployeeMaint >
{
   public PXSelect<EPEmployee> Employee;
   public PXSelect<Users> User;
}
//....
public class Users: IBqlTable
{
   [PXSelector(typeof(Select<Vendor>)] //- that one will return you Employee and not Vendor
   //....
}


So once again - when you select data, from database for PXSelect<BAccount> Acumatica needs to define what determine what table it should select, Because graph.Caches[typeof(BAccount)] is pointed on Vendor Cache system will define BAccount table as Vendor and not Baccount it self.

Solution
The best way to workaround it is to use BAccount2 table for PXSelect. When you use anither table it is different type so different separate cache will be create and all logic will work perfectly.

Anther way is to avoid such problems is to define data views in proper sequence. If you define views in following sequence:
public PXSelect<BAccount> Employee;
public PXSelect<Vendor> Employee;
public PXSelect<EPEmployee> Employee;
Than there will not be any issue, but please note that you may have some inconsistency of logic if your Vendor customize any first of BAccount.

If you faced it with standard Acumatica code, there is dirty and not recommended workaround for that fix for that - You can fix it by replacing of the cache to correct instance, you can read more here.
PXCache<Vendor> vendor = new PXCache<Vendor>(Base);
PXCache<EPEmployee> employee = new PXCache<EPEmployee>(Base);

Base.Caches[typeof(Vendor)] = vendor;
Base.Caches[typeof(EPEmployee)] = employee;


Have a nice development!

No comments: