Adapters, BAQs and searches
An adapter is the client-side wrapper around a business object. PartAdapter talks to Erp.BO.Part, DynamicQueryAdapter talks to Ice.BO.DynamicQuery, and so on. A customization uses adapters whenever it needs data the form hasn’t loaded, or wants to create or change records other than the one on screen.
The adapter pattern
Section titled “The adapter pattern”Every adapter call follows the same shape: create it against the form, connect, call, read the dataset, dispose.
private string GetPartDescription(string partNum){ string desc = ""; PartAdapter partAdapter = new PartAdapter(oTrans); try { partAdapter.BOConnect(); bool found = partAdapter.GetByID(partNum); if (found && partAdapter.PartData.Part.Count > 0) { desc = partAdapter.PartData.Part[0].PartDescription; } } finally { partAdapter.Dispose(); } return desc;}- Add the adapter’s assembly references first (Reference Adapter/BL Assemblies wizard, see The Script Editor and events).
- The dataset property is named after the business object:
PartData,VendorData,UD01Data,JobEntryData. - Depending on the adapter, a missing record makes
GetByIDreturnfalseor throw a “record not found” exception. Handle both, and check the row count before reading[0]. - Always
Dispose(). Atry/finally(orusing) block makes sure you do even when a call throws.
Following the screen’s method sequence
Section titled “Following the screen’s method sequence”When you use an adapter to create something, call the same methods in the same order that the Epicor screen does. Business objects expect their GetNew... and Change.../...Info methods to run first; skip one and the save fails or saves incomplete data.
Creating a customer shipment for a sales order, for example, looks roughly like this:
CustShipAdapter ship = new CustShipAdapter(oTrans);ship.BOConnect();string msg;
ship.GetNewShipHead();ship.GetHeadOrderInfo(orderNum, out msg); // fills customer, ship-to etc. from the ordership.Update();int packNum = ship.CustShipData.ShipHead[0].PackNum;
ship.GetNewOrdrShipDtl(packNum, orderNum);ship.CustShipData.ShipHead[0].RowMod = "U"; // keep the header flagged as changed while adding linesship.GetOrderInfo(orderNum, out msg);ship.GetOrderLineInfo(0, orderLine, partNum);ship.GetOrderRelInfo(0, orderRel, true);ship.GetQtyInfo(0, shipQty, 0m);ship.Update();
ship.Dispose();The way to learn a sequence like this is a client trace: turn on tracing, do the task by hand on the screen, and read which methods were called and with what values. Find the method a screen calls explains tracing. The community Epicor Trace Differ utility (see Sources) makes long traces easier to read by showing what changed in the dataset between calls.
Running a BAQ
Section titled “Running a BAQ”DynamicQueryAdapter runs any shared BAQ by ID. Parameters go into a QueryExecutionDataSet.
private DataTable RunPartStockQuery(string partNum){ DynamicQueryAdapter dqa = new DynamicQueryAdapter(oTrans); try { dqa.BOConnect();
QueryExecutionDataSet qeds = new QueryExecutionDataSet(); var param = qeds.ExecutionParameter.NewRow() as QueryExecutionDataSet.ExecutionParameterRow; param.ParameterID = "PartNum"; param.ParameterValue = partNum; param.ValueType = "nvarchar(50)"; param.IsEmpty = false; qeds.ExecutionParameter.Rows.Add(param);
dqa.ExecuteByID("XX_PartStock", qeds); return dqa.QueryResults.Tables["Results"].Copy(); } finally { dqa.Dispose(); }}ParameterIDmust match the BAQ parameter name exactly, andValueTypeshould match its type (nvarchar(50),integer,date…). For queries without parameters, pass the emptyQueryExecutionDataSet.- Results come back in the
Resultstable, with columns named by alias:Part_PartNum,Calculated_OnHand.Copy()the table if you keep it after disposing the adapter.
A typical use is showing extra figures on an entry form, such as on-hand and available quantity for the part on the current quote line. Run the BAQ from the line view’s EpiViewNotification (when the row changes), write the values into unbound text boxes, and blank them when the row is -1 or when the user clicks Clear or New.
Validating what the user typed
Section titled “Validating what the user typed”For a free-typed value, validate it when the user leaves the control. SearchFunctions.listLookup runs an adapter’s search silently, with a where clause, and returns the list dataset:
private void txtPart_Validated(object sender, System.EventArgs args){ string typed = txtPart.Text.Trim(); if (typed.Length == 0) { txtPartDesc.Text = ""; return; }
bool recSelected; string where = "PartNum = '" + typed.Replace("'", "''") + "'"; DataSet ds = SearchFunctions.listLookup(oTrans, "PartAdapter", out recSelected, false, where);
if (ds != null && ds.Tables[0].Rows.Count > 0) { DataRow part = ds.Tables[0].Rows[0]; txtPart.Text = part["PartNum"].ToString(); // fixes upper/lower case txtPartDesc.Text = part["PartDescription"].ToString(); } else { txtPartDesc.Text = ""; MessageBox.Show("Part " + typed + " was not found."); }}Escape single quotes in anything you put into a where clause, as above.
Search buttons: the Simple Search wizard
Section titled “Search buttons: the Simple Search wizard”To give users a search dialog that fills your custom fields, use Wizards > Customization Wizards > Simple Search:
- Add a button (and the text boxes or combo you want filled) to the form.
- Launch the wizard, click Get Adapters and pick the search adapter, for example
CustCntAdapterfor contacts. - Choose SearchDialog (or DropDown to feed a custom
EpiCombo). - Map each search field to a data view column, click Add for each, then Finish.
- In the button’s
Clickhandler, call the method the wizard generated, named likeSearchOnCustCntAdapterShowDialog().
Hooking the form’s own adapter calls
Section titled “Hooking the form’s own adapter calls”The form’s main adapter raises BeforeAdapterMethod and AfterAdapterMethod around every business object call it makes. Add a handler with the Form Event Wizard (pick the adapter, for example oTrans_adapter), then switch on the method name:
private void oTrans_adapter_AfterAdapterMethod(object sender, AfterAdapterMethodArgs args){ switch (args.MethodName) { case "ChangeVendor": // the supplier just changed: default a UD email field from its primary contact if (edvHead.Row > -1) { int vendorNum = (int)edvHead.dataView[edvHead.Row]["VendorNum"]; edvHead.dataView[edvHead.Row]["XX_SendToEmail_c"] = GetPrimaryContactEmail(vendorNum); } break; }}AfterAdapterMethodis for reacting: fill fields, refresh custom panels, show a message after a save.BeforeAdapterMethodcan stop the call: setargs.Cancel = trueafter warning the user.- Don’t know the method name? Uncomment the wizard’s
EpiMessageBox.Show(args.MethodName)line and use the screen for a minute.
Sources
This page is written in our own words. It was informed by: