How To Avoid Copy Paste With Help Of Attributes In Acumatica

 

Hello everybody,

today I want to leave a short note on how to avoid Copy/paste with help of custom attributes. 

Imagine following scenario. You have some set of duplicated code, which you need to apply at FieldSelecting . One of the ways of achieving this can be creation of some class and method within this class, which will handle that functionality, and then just copy/paste creation of the instance of the class at any place, where you need to have that business logic applied.

But you can use another way. You can use custom attributes. You can create your attribute, and then use that attribute over all places, where you may have a need for calling your business logic. In order to work that properly you'll need to inherit your Attribute from PXEventSubscriberAttribute, IPXFieldSelectingSubscriber . For example you can accomplish it like this:

 

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Parameter | AttributeTargets.Method)]
public class SomeAttribute : PXEventSubscriberAttributeIPXFieldSelectingSubscriber
{
    protected Type _TargetField;
    protected Type _DacType;
    
 
    public SomeAttribute(Type dacType, Type targetField)
    {
        _DacType = dacType;
        _TargetField = targetField;
    }
 
    public virtual void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
    {
var graph = sender.Graph;
//and some other logic
}

Also this can be applied to the field like this:

 public class YourDacClass

{

    [Some(typeof(DacClass), typeof(DacClass.someField))]
    [PXUIField(DisplayName = "Value")]
    public virtual string SomeField { getset; }

}

 

and that's it. Now you can have move your duplication logic to attribute SomeAttribute, and enjoy smaller amount of code.

 

 

Purpose Of Rowpersisting Event

 

Hello everybody,

today I want to leave a note on usage of RowPersisting event.

Quite often I see situations, when RowPersisting is used for making additional insertions to database. Also quite often I see cases when some additional inserts being performed to database. 

I want to warn against such an approach. Reason for that is that during RowPersisting event, Acumatica opens transaction scope. Because of that, additional readings from db, or additional persists to db in scope of RowPersisting may lead to performance degradation and even deadlocks. 

Purpose of RowPersisting event is kind of latest resort, in which you can modify your record before putting it to database. And it shouldn't be used for some other purposes. Other purposes of RowPersisting event is validate record before it was putted to database, or cancel commit operation through throwing of an exception.

 

 

 

 

 

 

Lightweight Persist To Database

 

Hello everybody,

today I want to describe following use case. Quite often it is needed to persist to database one or another DAC class, which is filled by some data. 

As usually I see people do this via hard coding of DAC class inside of the Graph. But today I want to share with you a way of persisting DAC class without hardcoding it as a view. 

In order to accomplish this, you can use following graph:

public class ImportEntitiesInsertion : PXGraph<ImportEntitiesInsertion>
{
    public string AddView(Type dacType)
    {
        var viewName = "_DYNAMIC_" + dacType.GetLongName();
        if (!this.Views.ContainsKey(viewName))
        {
            var command = BqlCommand.CreateInstance(typeof(Select<>), dacType);
            var newView = new PXView(thistrue, command);
            Views.Add(viewName, newView);
            Views.Caches.Add(dacType);
        }
        return viewName;
    }
}

 

After that, in some other place of the code, you can use this graph like this:

 

var graphForInsertion = PXGraph.CreateInstance<ImportEntitiesInsertion>();
var dacType = typeof(SOOrder); 
var viewName = graphForInsertion.AddView(dacType);
 
for (int i = 0; i < 10; i++)
{
    var newOrd = new SOOrder();
    graphForInsertion.Views[viewName].Cache.Insert(newOrd);
}
graphForInsertion.Persist();

 

 What I especially like about this approach, is that records will be persisted initially in the cache, and only after you'll call Persist, all bunch of records will be persisted to database.

 

 

How To Override Properly Creatematrixitems

 

Hello everybody,

today I want to leave a short snippet on how to override methods in CreateMatrixItemsImpl graph extension. Below goes code snippet you can use for this purpose:

 

public class CreateMatrixItemsImplExt : PXGraphExtension<CreateMatrixItems.CreateMatrixItemsImpl, CreateMatrixItems>
{
    public override void Initialize()
    {
        base.Initialize();
    }
}

With help of this code fragment you can override and customize a bit more Matrix management of Acumatica.

Execution Timeout Expired Error Message In Acumatica

 

Hello everybody,

today I want to leave a short note on Execution Timeout Expired. In case if you face it on your local dev environment, then consider adding following key in your web.config:

<add name="PXSqlDatabaseProvider" type="PX.Data.PXSqlDatabaseProvider, 
PX.Data" ... queryTimeout="100" />

With that addition, you'll be able to execute long running queries without timeout. 

NB

But keep in mind, that such addition can affect performance of Acumatica for some cases. So use that freely for your dev instance, and be careful with your production instance.

 

How To Make Selector For Csanswers

 

Hello everybody,

recently one of the colleagues asked me how to make selector from Attributes values. Also that request seem trivial, but still took some time, especially with usage of FBQL query to build. 

Below goes template you may use if you'll need some kind of selector for attributes by some predefined value:

 

public class SOOrderExt : PXCacheExtension<SOOrder>
    {
        public class Codes
        {
            public const string MediaCode = "MEDIACODE";

            public class mEdiaCode : BqlType<IBqlStringstring>.Constant<mEdiaCode>
            {
                public mEdiaCode() : base("mEdiaCode")
                {
                }
            }

            public const string OrdOrigin = "ORDORIGIN";

            public class ordOrigin : BqlType<IBqlStringstring>.Constant<ordOrigin>
            {
                public ordOrigin() : base("ORDORIGIN")
                {
                }
            }
        }

        [PXSelector(typeof(SearchFor<CSAnswers.value>.Where<CSAnswers.attributeID.IsEqual<Codes.ordOrigin>>))]
        [PXDBString(50)]
        public string SomeValue1 { getset; }

        [PXSelector(typeof(SearchFor<CSAnswers.value>.Where<CSAnswers.attributeID.IsEqual<Codes.mEdiaCode>>))]
        [PXDBString(50)]
        public string SomeValue { getset; }
    }

 

 For me it was also interesting to note, that in the past it was common to use for Selector combination of PXSelector with Search, but in FBQL you'll need PXSelector with SearchFor.

How To Connect Acumatica With Mysql At Api Level

 

Hello everybody,

today I want to leave a short note on how to connect Acumatica with MySql. Recently for me was needed to organize connection between API project which send requests to Acumatica and MySql database. In codebase I've seen following line:

 using MySqlX.XDevAPI.Relational;

 But when I've tried to find nuget package MySqlX, I've find nothing useful. After a bit of googling I've discovered that it is needed to have referenced MySql.Data package. After I've added MySql.Data package on my project, all MySqlX staff become active, and I was able to build and execute my project

 

 

Create Index With Include Section

 

Hello everybody.

Today I want to leave a short note on include section of SQL queries. Imagine that you see SQL Query similar to this:

 CREATE NONCLUSTERED INDEX [tt_CSAttributeGroup] ON [dbo].[CSAttributeGroup] ([CompanyID], [EntityClassID], [EntityType], [IsActive])  INCLUDE ([AttributeCategory], [CreatedByID], [CreatedByScreenID], [CreatedDateTime], [DefaultValue], [LastModifiedByID]) WITH (ONLINE = ON)

 And while part in the first brackets is clear, then second part Include is not very clear from the first point of view. 

What is difference between index which has INCLUDE section and index which doesn't have INCLUDE section?

Include section means, that values will be stored with the key itself, but will not be part of the key. 

In case of presented SQL query, which is used by Acumatica, key values will be created based on CompanyID, EntityClassID, EntityType, IsActive columns. They will be used for building nodes of the tree. And also each node of the tree besides having Key calculated will also have those columns: AttributeCategory, CreatedByID, CreatedByScreenID, CreatedDateTime, DefaultValue, LastModifiedByID .

How this fact may be used later?

Once SQL server will search for some field, based on CompanyID, EntityClassID, EntityType, IsActive columns in it's query, and will find them, SQL will not need to go to the data itself for columns AttributeCategory, CreatedByID, CreatedByScreenID, CreatedDateTime, DefaultValue, LastModifiedByID. But will read values of those columns from the tree itself. 

If to go further with illustration, then at storage level B+tree structure without include section will look like this:

but with include section it will look like this:

Summary

If to compare indexes with INCLUDE and indexes without INCLUDE section, you can make following conclusion: indexes with include section will make faster reading from database. But they will cause longer insert/update operations, as each change will cause copy/paste overhead. And if you have a lot of columns included, then each insert/update will have double loading on your system.

 

 

 

 

 

 

How To Clean Duplicated Elements From Acumatica Database

 

Hello everybody,

Today I want to leave a short snapshot which addresses following issue. 

One of my friends got table in Acumatica created, but for some reason at DB level he decided not to set there any field as part of Primary key. He had IsKey attributes set only at DAC class of Acumatica, but not at Database level. As outcome he got duplicated items at database level, and it was needed somehow to delete duplicated items. How to achieve it? 

After a bit of research, I've come with T-SQL code like this:

DELETE  Record
FROM    (
			SELECT  rowno = ROW_NUMBER() 
				OVER (
				PARTITION BY ACGLiquidFamilyPrice.LiquidFamilyCD, ACGLiquidFamilyPrice.PriceEntryDateTime, ACGLiquidFamilyPrice.CompanyID
				ORDER BY ACGLiquidFamilyPrice.LiquidFamilyCD, ACGLiquidFamilyPrice.PriceEntryDateTime, ACGLiquidFamilyPrice.CompanyID )
			FROM  ACGLiquidFamilyPrice
        
        ) AS Record
WHERE   Record.rowno > 1

Columns LiquidFamilyCD, PriceEntryDateTime, CompanyID - are key fields.

ROW_NUMBER in connection with Partition by allows to delete only records, which are more then one. 

 

 

Covid 19 Dealing

 

Hi everybody,

It happened. I've got COVID-19 myself. As well as my wife. Very unpleasant feelings honestly speaking. 

On the day 1 of symptoms I've got those:

a. Temperature ( ~38 )

b. Headache

c. Weakness

Now I have one more concern about my parents. I'm worried if they got or not got COVID-19. And in case if yes, I'll have one more reason to worry.

Update on 2020-07-16

As of now by being in hospital I've got some improvements in my health. Temperature become lower. But now  I got another symptom: caught with blood. So my advice for anyone who says that COVID-19 is fake is this: it is not fake, but very real disease. Don't look on it lightly.

Update on 2020-07-24

I was sent to continue dealing with Covid-19 to home. Luckily for me, treatment went fine, and I'll continue on way of recovering. 

Update on 2020-08-11

I got two negative PCR tests on Covid-19. Besides that I have also regained my working memory back. During Covid-19 my working memory was affected. In the middle of the sentence I've used to forget what I supposed to say. But for now I don't have this issue which makes me very happy as of now.