Acumatica: инициализируйте представление только один раз, чтобы повысить производительность или удобство работы - PullRequest
1 голос
/ 10 марта 2020

У меня есть простое представление:

public PXSelect<MPEmployeeTermination, Where<MPEmployeeTermination.employeeID,
        Equal<Current<TerminationFilter.employeeID>>,
        And<MPEmployeeTermination.payRunID, Equal<Current<TerminationFilter.payRunID>>>>> EmployeeTerminations;

и метод, который его инициализирует:

public IEnumerable employeeTerminations()
    {
       return _terminationHandler.GetEmployeeTerminations(EmployeeTerminations, EmployeeTerminationItems, OtherTerminationPayments); 
    }

Метод GetEmployeeTerminations (возвращает IEnumerable) тяжелый и работает до часто - любая операция в этом интерфейсе. Я не могу найти никакого контроля, когда вызывается этот метод, тогда как метод GetEmployeeTerminations может быть вызван только один раз при инициализации (когда вызывается конструктор). Я могу использовать поле Boolean stati c, чтобы запустить метод один раз, но я считаю, что существуют другие рекомендованные шаблонами Acumatica, такие как атрибуты, et c. Можно установить EmployeeTerminations = GetEmployeeTerminations, но неясно, как конвертировать IEnumerable в PXSelect<MPEmployeeTermination>. Как я могу сделать это в соответствующем паттерне Acumatica или же существует лучшая модель, чем просто поле * stati c?

UI: enter image description here

enter image description here

Буду благодарен любому совету или предложению.

Я обновляю свой вопрос :

Конструктор вызывает каждый время, когда изменяется любое редактируемое свойство в пользовательском интерфейсе. Решение с stati c boolean не работает, потому что, когда я открываю и закрываю пользовательский интерфейс, поле stati c уже сохраняется. Если поле не является stati c - оно будет сбрасываться каждый раз при запуске конструктора, следовательно, это также не будет работать.

Этот метод

_terminationHandler.GetEmployeeTerminations(EmployeeTerminations, EmployeeTerminationItems, OtherTerminationPayments)

вычисляет значения для "master" "DA C MPEmployeeTermination, а также для" деталей "DA C MPEmployeeTerminationItem. DA C MPEmployeeTerminationItem отвечает за 2 сетки данных в мастере и вычисляет данные в обоих ЦАП, связывается с удаленным сервером и т. Д. c. Иногда это работает быстрее, иногда медленнее, но субъективное время пользователя кажется слишком медленным: enter image description here enter image description here

И всегда в конце последовательности мы имеем таинственный tread.sleep, который занимает больше времени, чем наша операция, и происходит за кулисами Acumatica: enter image description here Я считаю, что рекомендация слишком запускать этот метод только один раз при загрузке. Какое лучшее место для этого? Даже конструктор работает здесь, когда каждое свойство в DA C изменяется.

Другой тяжелый метод вызывается в событии MPEmployeeTermination_TerminationDate_FieldUpdated и не может быть перемещен в другое место и должен выполнять все вычисления, когда эта дата изменилась.

Основная идея слишком улучшить пользовательский опыт и предотвратить момент, когда пользовательский интерфейс застрял. Может быть, я могу использовать асинхронное программирование?

Например, если я изменю метод на

 public async Task<IEnumerable> employeeTerminations()
    {
        return await Task.Run(() => _terminationHandler.GetEmployeeTerminations(EmployeeTerminations, EmployeeTerminationItems, OtherTerminationPayments));
    }

Пользовательский интерфейс больше не застревает, но я не уверен во всех возможных побочных эффектах на платформе Acumatica , И это событие такое MPEmployeeTermination_TerminationDate_FieldUpdated из-за ограничения Acumatica.

Ответы [ 4 ]

1 голос
/ 12 марта 2020

Что ж, если вы действительно хотите запустить этот метод только один раз при загрузке - вы должны сделать следующее

  1. Добавить новый stati c флаг - Initialized например,
  2. Переписать действие PXCancel (перехватить событие, когда ключи сущностей были изменены) и сбросить там свой флаг
  3. Переписать PXFirst, PXPrevious, PXNext, PXLast действий (чтобы перехватить все события навигации) и сбросить там свой флаг
  4. Используйте флаг Initialized, чтобы запустить нужный метод только один раз

см. Код ниже (я взял Экран клиентов в качестве примера)

private static bool Initialized = false;

public PXCancel<Customer> cancel;
public PXFirst<Customer> first;
public PXPrevious<Customer> prev;
public PXNext<Customer> next;
public PXLast<Customer> last;

[PXCancelButton]
protected new virtual IEnumerable Cancel(PXAdapter a)
{
    Initialized = false;
    foreach (Customer record in new PXCancel<Customer>(this, "Cancel").Press(a))
        yield return record;
}

[PXFirstButton]
protected new virtual IEnumerable First(PXAdapter a)
{
    Initialized = false;
    foreach (Customer record in new PXFirst<Customer>(this, "First").Press(a))
        yield return record;
}

[PXPreviousButton]
protected new virtual IEnumerable Prev(PXAdapter a)
{
    Initialized = false;
    foreach (Customer record in new PXPrevious<Customer>(this, "Prev").Press(a))
    {
        return BAccount.Cache.GetStatus(record) == PXEntryStatus.Inserted
            ? last.Press(a)
            : new object[] { record };
    }

    return new object[0];
}

[PXNextButton]
protected new virtual IEnumerable Next(PXAdapter a)
{
    Initialized = false;
    foreach (Customer record in new PXNext<Customer>(this, "Next").Press(a))
    {
        return BAccount.Cache.GetStatus(record) == PXEntryStatus.Inserted
            ? first.Press(a)
            : new object[] { record };
    }

    return new object[0];
}

[PXLastButton]
protected new virtual IEnumerable Last(PXAdapter a)
{
    Initialized = false;
    foreach (Customer record in new PXLast<Customer>(this, "Last").Press(a))
        yield return record;
}

Я тестировал такой подход с CustomerMaint.billContact делегатом - отлично работает на моем экземпляре

private static IEnumerable contactCurrent;

[Api.Export.PXOptimizationBehavior(IgnoreBqlDelegate = true)]
protected virtual IEnumerable billContact()
{
    if (!Initialized)
    {
        Initialized = true;

        Contact cnt = null;
        Customer customer = this.BAccount.Current;
        if (customer != null && customer.DefBillContactID != null)
        {
            cnt = FindContact(customer.DefBillContactID);
            if (cnt != null)
            {
                if (customer.IsBillContSameAsMain == true)
                {
                    cnt = PXCache<Contact>.CreateCopy(cnt);
                    PXUIFieldAttribute.SetEnabled(this.BillContact.Cache, cnt, false);
                }
                else
                {
                    PXUIFieldAttribute.SetEnabled(this.BillContact.Cache, cnt, true);
                }
            }
        }
        return contactCurrent = new Contact[] { cnt };
    }

    return contactCurrent;
}

, и ваш метод будет таким

private static IEnumerable employeeTerminationsCurrent;

public IEnumerable employeeTerminations(PXAdapter a)
{
    if (!Initialized)
    {
        Initialized = true;
        return employeeTerminationsCurrent = _terminationHandler.GetEmployeeTerminations(EmployeeTerminations, EmployeeTerminationItems, OtherTerminationPayments);
    }

    return employeeTerminationsCurrent;
}
0 голосов
/ 18 марта 2020

Спасибо вам всем, ребята, за ваше время и усилия. Я пробовал предлагаемые решения, и ничего не работало должным образом из-за сложности функции, которую мне нужно улучшить. Улучшение пользовательского опыта было сделано за счет уменьшения вызова тяжелой функции:

_terminationHandler.GetEmployeeTerminations(EmployeeTerminations, EmployeeTerminationItems, OtherTerminationPayments);

Значит, я удалил переопределение делегата представления:

 public IEnumerable employeeTerminations()
        {
            return TerminationHandler.GetEmployeeTerminations(EmployeeTerminations, EmployeeTerminationItems, OtherTerminationPayments);
        }

Вместо этого метода, который вызывается каждый раз, когда изменяется каждое свойство EmployeeTerminations, я вызываю его, когда изменяется свойство TerminationDate:

 public virtual void MPEmployeeTermination_TerminationDate_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
    {
        var row = e.Row as MPEmployeeTermination;
        if (row.IsNull()) return;
        TerminationHandler.GetEmployeeTerminations(EmployeeTerminations, EmployeeTerminationItems, OtherTerminationPayments);

    }

Так что вместо борьбы с инфраструктурой я улучшил BL и в 90 % случаев улучшен пользовательский опыт. Надеюсь, это поможет кому-то в подобной ситуации.

0 голосов
/ 16 марта 2020

Я вижу эти подходы с помощью механизмов кеширования Acumatica:

  1. Реализация IPrefetchable и кеширование ваших значений с помощью PXDataBaseSlot. Здесь является одним примером описания. И другой один.
  2. Вы PXContext и слоты. Вот также описание .
  3. Используйте Cache.Insert, который может помочь вам хранить данные между вызовами. Это может выглядеть так:

    public PXSelect<MPEmployeeTermination, Where<MPEmployeeTermination.employeeID,
    Equal<Current<TerminationFilter.employeeID>>,
    And<MPEmployeeTermination.payRunID, Equal<Current<TerminationFilter.payRunID>>>>> EmployeeTerminations;
    
    public IEnumerable employeeTerminations()
    {
       bool anyFound = false;
       foreach(var row in EmployeeTerminations.Cache.Inserted)
       {
          anyFound = true;
          yield return row;
       }
    
      if(!anyFound)
      {
         var rows = _terminationHandler.GetEmployeeTerminations(EmployeeTerminations, EmployeeTerminationItems, OtherTerminationPayments);
         foreach(var row in rows)
         {
            var insertedRow = EmployeeTerminations.Insert(row);
            yield return insertedRow;
         }
      }
    

В случае третьего варианта записи также будут сохраняться в сеансе.

0 голосов
/ 10 марта 2020

Я не знаком с экраном, которым вы поделились, поэтому мне нужно придерживаться более общего ответа и надеяться, что он даст вам достаточно, чтобы преодолеть препятствие, пока кто-то другой не сможет дать лучший ответ. Я немного обеспокоен тем, как ваш фильтр встроен в вашу вкладку, но я не очень знаком с созданием такого рода структуры экрана. Мои экраны достаточно просты, поэтому мои фильтры всегда находятся в верхней части.

Как вы заметили, вы определяете конструктор для своего представления. Вы найдете много примеров в стандартном коде Acumatica, где представление контролируется с помощью конструктора. Как вы делаете, конструктор может быть использован для ограничения результатов, полученных в соответствии с некоторыми выбираемыми пользователем параметрами на экране. Это указывает на пример в PO.POCreate для конструкторов «filter» и «fixedDemand». Они заполняют свои соответствующие представления, и в fixedDemand есть несколько применимых слепков для интерпретации IEnumerable.

Возвращенный объект типа IEnumerable в конструкторе для представления - это структура представления в PXResultSet для определенного PXSelect. Вы можете перемещаться по значениям, распаковывая PXResultset, как показано в EnumerateAndPrepareFixedDemands.

public virtual IEnumerable EnumerateAndPrepareFixedDemands(PXResultset<POFixedDemand> fixedDemands)
{
    foreach (PXResult<POFixedDemand, InventoryItem, Vendor, POVendorInventory> rec in fixedDemands)
    {
        ...
    }
}

В вашем случае, я считаю, что IEnumerable будет рассматриваться как:

PXResultset<MPEmployeeTermination>

Предполагая, что структура ваш экран не вызывает чрезмерных накладных расходов (опять же, я не знаком с этим дизайном экрана) ...

В общем, если у вас возникают проблемы с производительностью, вы можете проверить, правильно ли ваш код использует индексы и, если вы можете сузить диапазон выбора. Единственный раз, когда у будет реальное снижение производительности, это будет когда возвращаемый набор результатов слишком велик для управления в памяти. Иногда исправление заключается в добавлении индекса, а иногда просмотр индексов говорит мне, что я пропустил ключевое поле в моем PXSelect. Вы можете рассмотреть запрос профилировщика в меню, чтобы записать выполняемые команды SQL и попробовать запустить их вручную для базы данных, чтобы проверить производительность и сузить ли результаты настолько сильно, насколько это необходимо. По крайней мере, вы можете быть удивлены тем, какие значения передаются в команды SQL, отправляемые в базу данных. (Это то, чему я обычно учусь из зарегистрированных SQL команд.)

Надеюсь, это поможет. Пожалуйста, обновите, если вы найдете что-то еще для решения.

...