Specifying empty date values

August 19, 2008

If you want to set a date field to an empty date or need to compare if a date field is empty, you can use the datenull() function. It is a method of the Global class and will simply return 011\1900.

This leads to more things:

  1. You can write dates in Axapta X++ editor using backslashes, e.g. 144\2006
  2. An empty date in Axapta is specified as January 1st, 1900.
  3. You
    can use methods from the Global class without having to write
    “Global::”. So, you can write “if date == datenull()” and you do not
    have to write “if date == Global::datenull()”. This works with all the
    methods from the Global class.
Advertisements

Using global search for virtual tables

August 19, 2008

The global search is a feature in Dynamics Ax 4.0 which allows you to
search for a string in several tables (like a full text search). You
setup some tables and fields to be searched in and start a data crawler
that collects the data to be searched.

Now, if some of your
tables that are searched belong to a virtual company, the data crawler
will mark them as records of the company it is running in. That means
that if you want to search for your virtual data from a different
company, it will return no results.

Example: you have the
companies xxx and yyy and a virtual company vir. Table CustTable is
virtual (dataareaid of the records is vir). The data crawler runs in
company xxx and will mark the CustTable records as belonging to company
xxx .
Now, if you start a search in company yyy, it will not find
the Custtable records as they seem to belong to a different company.
You could only set up a second data crwaler for company yyy which would
collect exactly the same records and you would need to store them twice
in your database.

The following changes will circumvent that:
you will be able to see data from different companies. There are some
drawbacks, however: you will be able to see search results from your
“data crawler company”. But it the data is from a non-virtual table,
you will not be able to see the results. But I hope it will lead you to
a way where you can make your own modifications to get the best out of
the global search. Remember: all changes you make are at your own risk.

Here are the changes you have to do:

Class SysGSSearchStart, method startSearch
comment the following line:

infolog.add(Exception::Warning,”@SYS98793″);

With that, there will be no warning if you are working in a company where the data crawler is not running.

Class SysSearch, method search:

at line 28, just after “if (!searchname)” add:

select firstonly RecId from sysSearchName
where sysSearchName.Design == ‘SDS_xxx_default’
&& sysSearchName.LanguageId == this.languageId();

if (!sysSearchName)

replace the xxx in the ‘SDS_xxx_default’ with the company id where the data crawler is running.

Class SysSearch, methods searchWord and searchExactWord:

at line 11, replace the “where sysSearchName.Design == this.design() &&” with:

where (sysSearchName.Design == this.design()
sysSearchName.Design == ‘SDS_xxx_default’) &&

again, replace the xxx in the ‘SDS_xxx_default’ with the company id where the data crawler is running.

Class SysSearchDoDataSearch, method buildItemListXML:

at line 11, after a while select indextable from sysDataSearch block, add the following code:

changecompany(‘xxx’)
{
sysDataSearch = null;
while select IndexTable from sysDataSearch
{
dictTable = new DictTable(sysDataSearch.IndexTable);
if (dictTable.rights() != AccessType::NoAccess)
searchTableMap.insert(dictTable.id(),0);
}
}
sysDataSearch = null;

Replace ‘xxx’ with the company id where the data crawler is running.

At line 77, after select sysDataSearch where sysDataSearch.SearchGroupId == m_eSearchGroupDef && sysDataSearch.IndexTable == tableid; add the following code:

if (!sysDataSearch)
{
changecompany(‘xxx’)
{
sysDataSearch = null;
select sysDataSearch
where sysDataSearch.SearchGroupId == SearchGroupDef
&& sysDataSearch.IndexTable == tableid;
}
}

Replace ‘xxx’ with the company id where the data crawler is running.


Using the Windows API from within Axapta

August 19, 2008

It seems that calling Windows API functions from Axapta is viewed as some
sort of mysterious magic by most Axapta developers. Unfortunately,
there are times when you need to do something that requires tight
integration with functionality that exists outside of the system. While
there are other methods of interfacing between bits of code, including
COM, XML-RPC, or even .NET, using the Windows API can sometimes be very
efficient, and powerful.

For beginner WinAPI coders who are also Axapta programmers, with at least some programming experience in C or C++,
I felt it might be better to answer a question from my colleagues about
calling WinAPI calls from Axapta, outside of those already wrapped by
the WinAPI:: or WinGDI:: classes.

Axapta
provides basic functionality to open a handle to a library (DLL file),
and a handle to a function within that library. The functionality is
handled within two classes, DLL:: and DLLFunction:: respectively, and allows you to call practically any piece of code within any dynamic link library.

The most common use for this is naturally to make calls into the
Windows API, but there’s no reason why you can’t provide your own
functions within a DLL and call them from Axapta, but be aware that the
DLL file must be installed on either the client or server, depending on
where the code will execute.

The DLL:: class opens a DLL file and acts as a handle for DLLFunction::
to use when defining handles for calls. If the DLL isn’t already opened
by Axapta internally (as would be the case with the standard Windows
API DLL files), it will also silently handle the opening,
initialisation, and closure of the DLL in conjunction with the garbage
collector.

The DLLFunction:: class defines a handle to a function’s
symbol within the DLL, and defines the return value and the parameters
of the function. This is important to define correctly, as defined by
the C/C++ headers; otherwise you will head straight into problems with
Axapta crashing, either during the call, or later on. Don’t worry, this
is easily determined, and I’ll explain how this works. If you don’t
have the header files (*.h) with a windows compiler, you can look at Google Code Search.

Before going into detail with how this works, you might need to know
what’s going on behind the scenes, especially how functions call other
functions from the computer’s perspective. Since that’s not really in
scope here, I’ll very quickly describe only the elements of this you
need to know to make your calls work from Axapta. If you already know
this, you can skip past it.

When you call a function, its parameters get pushed onto the stack, which is a FILO
data model. The function being called then takes these items off the
stack and can use them as variables. The calling function knows
precisely how many items it needs to put onto the stack, and the
function itself knows how many items it needs to take off the stack.

As you know, once a called function has done its work, it can return a value. The process works the same way in reverse, pushing the return value onto the stack for the caller to pop from the stack.

Sometimes an example helps, so let’s consider the following simplistic example in C:

int foo(int bar, char* baz)
{
printf(“Qux: %s”, baz);

return (bar * 2);
}

void quux(void)
{
int corge = foo(10, “Grault”);
}

Consider when the quux() function calls foo(), it will push the value 10 and a pointer to the null-terminated string “Grault” onto the stack, and jump to the location in memory where the code for foo() exists. Subsequently, foo() will pop these two values from the stack to be able to use them. When foo() returns, it will push the value of 20 onto the stack, and quux() will pop that from the stack and use it as the value for corge.

Of course, there’s a lot more going on in the stack, in fact there are a few stacks, but we don’t need to cover that here.

To be able to call your function, you will need to know which DLL
your function is in, and the real name of the function as exported by
the DLL’s symbols. I mention this because it seems obvious, but you
should remember that Microsoft generally provide two versions of
functions if they use strings, for ANSI and wide-characters, normally
with an “A” or “W” suffix respectively. The headers
for these functions often hide this fact, as does MSDN. Opt for the
ANSI version – Axapta will convert strings based on the locale.

To cover the subject quickly, let’s build a small example form that
uses the Windows API to draw the appropriate icon for a specified file
within a window pane.

Build a form with a Window control. In this example, I have named the window Icon. The window control needs have the width and height both set to 32 pixels (icon size), and the AutoDeclaration property enabled.

The form needs some code. Add some variables we will need to the class declaration of the form:

public class FormRun extends ObjectRun
{
// DLL file handles
DLL shellDLL; // shell32.dll
DLL userDLL; // user32.dll

// Function call handles
DLLFunction extractAssociatedIconFunc;
DLLFunction destroyIconFunc;
DLLFunction drawIconFunc;

// Our icon handle, taken from windows
int hIcon;

// These are from the windows headers, used later in the code!
#define.DI_MASK(0x01)
#define.DI_IMAGE(0x02)
#define.DI_COMPAT(0x04)
}

We need to initialise many of these variables first. Using the init()
function on the form, we can initialise the DLL handles, and the
functions. The DLL handles only need the name of the DLL file,
obviously, but the DLL function handles need to be configured with the
primitive types that will be appearing on the stack. Even parameters or
the return value are not used, you must specify them, or the stack will
be out of synchronisation.

To help you define your primitive types, Axapta has a kernel enumerator called ExtTypes,
which contains all the basic types you will need. You need to then take
what’s listed in the header file and determine what primitive type it
is. For example, windows defines many functions which return a value of
BOOL, however this is simply a type definition in C for an
integer. It’s your responsibility to match these values up logically by
working through the definitions. Don’t worry; I’ve done the hard work
for you in this example!

Override your form’s init() function with the following code:

public void init()
{
;

// Initialise the form first
super();

// Set up our call for ExtractAssociatedIcon()
extractAssociatedIconFunc =
new DLLFunction(shellDLL, “ExtractAssociatedIconA”);
extractAssociatedIconFunc.returns(ExtTypes::DWord); // HICON
extractAssociatedIconFunc.arg(ExtTypes::DWord, // HINSTANCE hInst
ExtTypes::String, // LPTSTR lpIconPath
ExtTypes::Pointer); // LPWORD lpiIcon

// We also need a function to destroy the icon handle once we’ve used it
destroyIconFunc = new DLLFunction(userDLL, “DestroyIcon”);
destroyIconFunc.returns(ExtTypes::DWord); // BOOL
destroyIconFunc.arg(ExtTypes::DWord); // HICON hIcon

// This is our call for drawing the icon onto a window
drawIconFunc = new DLLFunction(userDLL, “DrawIconEx”);
drawIconFunc.returns(ExtTypes::DWord); // BOOL
drawIconFunc.arg(ExtTypes::DWord, // HDC hDC
ExtTypes::DWord, // int xLeft
ExtTypes::DWord, // int yTop
ExtTypes::DWord, // HICON hIcon
ExtTypes::DWord, // int cxWidth
ExtTypes::DWord, // int cyWidth
ExtTypes::DWord, // uint istepIfAniCur
ExtTypes::DWord, // HBRUSH hbrFlickerFreeDraw
ExtTypes::DWord); // UINT diFlags
}

When the form runs, we want the icon to be updated with an icon from a file. To do this, we will override the run method on the form. After the form has started normally, we will make a call to ExtractAssociatedIcon() to find our hIcon (icon handle) value. We’ll save this value for later use. Once this is done, we’ll need to draw the icon for the first time.

Axapta provides one additional bit of help here for complex
structures containing primitive types, or any other place where you
need to pass a pointer on the stack. The kernel class Binary::
lets you allocate memory in the system, and have it freed by the normal
Axapta garbage collection routines. This of this as your answer to malloc(), and also your answer to creating and reading C-style structures.

Override your form’s run() method with the following, and feel free to change the file to any file on your system:

public void run()
{
Binary binary;
;

// Run the form normally
super();

/* We need to build a binary item for two bytes (a short), since windows
* has an “in/out” pointer for the icon information.
*/

binary = new Binary(2); // 2 bytes = short

// Call the ExtractAssociatedIcon() function
hIcon = extractAssociatedIconFunc.call(NULL,
“C:\\WINDOWS\\EXPLORER.EXE”,
binary);

// Make sure we got an icon (we should, if the file exists)
if (!hIcon) {
throw error(“Unable to get hIcon”);
}

// Do the initial drawing of the icon
this.drawIcon();
}

Since the Windows API will allocate stuff on its side, we need to
clean up some memory when the form is closed. To do this, we call DestroyIcon() with the icon’s handle. Override your form’s close() method with the following snippet:

public void close()
{
;

// If we have an icon handle, we need to clean up
if (hIcon) {
destroyIconFunc.call(hIcon);
}

// Close the form
super();
}

Now the fun part: We need to grab the device context of the window,
and use it to draw the icon on to the form. We will use the function DrawIconEx().
Note that we need to lock the device context of the window before
passing it outside for the library to chew on. Add the following method
to your form:

public void drawIcon()
{
;

// Lock the device context of the window so we can use it
Icon.lockDC();

// Paint the icon (we’ll use the window size to determine the icon size)
drawIconFunc.call(Icon.hDC(), 0, 0, hIcon,
Icon.widthValue(),
Icon.heightValue(),
0, NULL,
(#DI_IMAGE | #DI_MASK | #DI_COMPAT));

// Unlock the device context of the window
Icon.unlockDC();
}

Finally, you will also need to override the function paint()
on the Window control. The following code will be called by Axapta
whenever Windows asks Axapta to paint the window. This allows you to
redraw the icon when the form is covered or uncovered by another window
in the foreground, for example. Without this, your icon will disappear
if anything other than the mouse moves over it! Add this code to the Icon control:

public int paint()
{
int ret;
;

// Paint the window first
ret = super();

// Draw the icon
element.drawIcon();

return ret;
}


Color-code your Dynamics environments

August 15, 2008

Since implementing Axapta / Dynamics AX in 2003, we’ve found it invaluable to provide an environment for testing and training as well as our live system (or production environment) to end users. This lets people try different processes or train new employees without fear of screwing up the “real” data.

Occasionally, though, we’ve run into a problem where someone thought they were using the live system, but were actually in the test environment. It’s an easy mistake to make. There is no obvious visual cue to alert a user that they are working on a test system.

There is a way change the color of the Dynamics forms to help indicate what environment is in use. It involved overriding the SysSetupFormRun.run() method, which will be called every time a form is opened. On the class SysSetupFormRun, create a new method with this code:

public void run()
{
SysSQLSystemInfo systemInfo = SysSQLSystemInfo::construct();
;

super();

// Set the color scheme of this instance of the SysFormRun to RGB
this.design().colorScheme(FormColorScheme::RGB);

// If the database name is not the live version, change the color of the form

if (systemInfo.getloginDatabase() != ‘MyDBName’)
this.design().backgroundColor(0x112255);
}

That’s all there is to it. If your live and test systems use the same database name, but the AOS is running on different servers you can modify this code to to match on systemInfo.getLoginServer() != ‘MyServerName’. You can change the color by setting the hex value. It uses the RGB values in reverse order: 0xBBGGRR.

Variations of this code could switch form colors to indicate the current user account, application layer, or company. Less practical uses could match color to the time of day or season of the year. If you find this code useful, leave me a message noting what you did with it.


Number sequences

August 13, 2008

Number sequences

Number sequences in Axapta are a mechanism
for generating unique numbers. These are generally used as a unique Id to
identify a table record.

Every number sequence is
linked to an ExtendedDataType inside Axapta. Therefore you will need to create
separate datatypes for each number sequence which will be created.

Contents


*
1 Adding a number sequence

*
1.1 Add an element to the NumberSeqModule base enum

*
1.2 Extend the NumberSeqReference class

*
1.3 Create a parameters table and form

*
2 Calling a number sequence

Adding a number sequence

You may wish to use one or
more new number sequences when you create your own module inside Axapta.

The steps to add your own
number sequence to Axapta are fairly straight-forward, but lengthy. The
following description is based on the Axapta best-practice methodologies.

For this example, we will
add one new number sequence called NSId for a new module called NS. This
assumes that an ExtendedDataType called NSId already exists.

If not using an existing
extended datatype, create a new string EDT, which extends datatype ‘num’. Note
that in Ax 3.0 this num is a string field, length of 20.

Add an element to the NumberSeqModule base enum

One element should be added
to the enum for your module, irrespective of the number of actual number
sequences which will be created.

Therefore we modify the
NumberSeqModule enum and add the element NS.

Extend the NumberSeqReference class

Create a new sub-class of
NumberSeqReference and implement the numberSeqModule() and loadModule()
methods. Use an existing sub-class as a template to assist with this. This is
shown below.

public class NumberSeqReference_NS extends NumberSeqReference

{

}

/*

Detailed description of how to setup
references for number sequences can

be found i method loadModule() on the
‘father’ class: numberSeqReference.

*/

protected void loadModule()

{


NumberSequenceReference numRef;


;

// Setup Job TransId


numRef.dataTypeId =
typeid2extendedtypeid(typeid(NSId));


numRef.referenceHelp =
“Some description
here”
;


numref.WizardContinuous =
false;


numRef.WizardManual =
NoYes::No;


numRef.WizardAllowChangeDown =
NoYes::No;


numRef.WizardAllowChangeUp =
NoYes::No;


numRef.sortField = 1;


this.create(numRef);

}

public static NumberSeqModule numberSeqModule()

{

return NumberSeqModule::NS;

}

In addition, you must
modify the construct() and moduleList() methods on NumberSeqReference itself to
add references to your new module class. Note the sections of code surrounded
by //— NS Begin and //— NS End

static public container
moduleList()

{

container moduleList;


;


moduleList += NumberSeqReference_Bank::numberSeqModule();


moduleList += NumberSeqReference_BOM::numberSeqModule();


moduleList += NumberSeqReference_Customer::numberSeqModule();


moduleList += NumberSeqReference_Document::numberSeqModule();


moduleList += NumberSeqReference_ForeignTrade::numberSeqModule();


moduleList += NumberSeqReference_General::numberSeqModule();


moduleList += NumberSeqReference_Inventory::numberSeqModule();


moduleList += NumberSeqReference_Ledger::numberSeqModule();


moduleList += NumberSeqReference_Location::numberSeqModule();


moduleList += NumberSeqReference_MasterPlanning::numberSeqModule();


moduleList += NumberSeqReference_Production::numberSeqModule();


moduleList += NumberSeqReference_Project::numberSeqModule();


moduleList += NumberSeqReference_PurchaseOrder::numberSeqModule();


moduleList += NumberSeqReference_Route::numberSeqModule();


moduleList += NumberSeqReference_SalesOrder::numberSeqModule();


moduleList += NumberSeqReference_Tax::numberSeqModule();


moduleList += NumberSeqReference_Vendor::numberSeqModule();


moduleList += NumberSeqReference_Internet::numberSeqModule();


moduleList += NumberSeqReference_Asset::numberSeqModule();

//— HRM Begin


moduleList += NumberSeqReference_HRM::numberSeqModule();


moduleList += NumberSeqReference_KnowledgeCollector::numberSeqModule();


moduleList += NumberSeqReference_VirtualNetwork::numberSeqModule();

//— HRM End

//— CRM Begin


moduleList += NumberSeqReference_CRM::numberSeqModule();

//— CRM End

//— SFC Begin


moduleList += NumberSeqReference_JobManager::numberSeqModule();

//— SFC End

//— NS Begin


moduleList += NumberSeqReference_NS::numberSeqModule();

//— NS End

return moduleList;

}

public static numberSeqReference construct(NumberSeqModule _module)

{

switch (_module)

{


case (NumberSeqReference_Ledger::numberSeqModule()) : return new NumberSeqReference_Ledger(_module);


case (NumberSeqReference_Tax::numberSeqModule()) : return new NumberSeqReference_Tax(_module);


case (NumberSeqReference_Bank::numberSeqModule()) : return new NumberSeqReference_Bank(_module);


case (NumberSeqReference_SalesOrder::numberSeqModule()) : return new NumberSeqReference_SalesOrder(_module);


case (NumberSeqReference_ForeignTrade::numberSeqModule()) : return new NumberSeqReference_ForeignTrade(_module);

case (NumberSeqReference_Customer::numberSeqModule()) : return new NumberSeqReference_Customer(_module);


case (NumberSeqReference_PurchaseOrder::numberSeqModule())
:
return new NumberSeqReference_PurchaseOrder(_module);


case (NumberSeqReference_Vendor::numberSeqModule()) : return new NumberSeqReference_Vendor(_module);


case (NumberSeqReference_Inventory::numberSeqModule()) : return new NumberSeqReference_Inventory(_module);


case (NumberSeqReference_BOM::numberSeqModule()) : return new NumberSeqReference_BOM(_module);


case (NumberSeqReference_Route::numberSeqModule()) : return new NumberSeqReference_Route(_module);


case (NumberSeqReference_Production::numberSeqModule()) : return new NumberSeqReference_Production(_module);


case (NumberSeqReference_MasterPlanning::numberSeqModule()) :
return new NumberSeqReference_MasterPlanning(_module);


case (NumberSeqReference_Project::numberSeqModule()) : return new NumberSeqReference_Project(_module);


case (NumberSeqReference_Location::numberSeqModule()) : return new NumberSeqReference_Location(_module);


case (NumberSeqReference_Document::numberSeqModule()) : return new NumberSeqReference_Document(_module);


case (NumberSeqReference_General::numberSeqModule()) : return new NumberSeqReference_General(_module);


case (NumberSeqReference_Internet::numberSeqModule()) : return new NumberSeqReference_Internet(_module);


case (NumberSeqReference_Asset::numberSeqModule()) : return new NumberSeqReference_Asset(_module);


//
CC begin


case (NumberSeqReference_HRM::numberSeqModule()) : return new NumberSeqReference_HRM(_module);


case (NumberSeqReference_VirtualNetwork::numberSeqModule()) : return new NumberSeqReference_VirtualNetwork(_module);


case (NumberSeqReference_KnowledgeCollector::numberSeqModule())
:
return new NumberSeqReference_KnowledgeCollector(_module);


//
CC end


//
Shop Floor Control begin


case (NumberSeqReference_JobManager::numberSeqModule()) : return new NumberSeqReference_JobManager(_module);


//
Shop Floor Control end


//
CRM addition begin


case (NumberSeqReference_CRM::numberSeqModule()) : return new NumberSeqReference_CRM(_module);


//
CRM addition end


//
Product Builder addition begin

case (NumberSeqReference_PBA::numberSeqModule()) : return new NumberSeqReference_PBA(_module);


//
Product Builder addition end


//
NS begin


case (NumberSeqReference_NS::numberSeqModule()) : return new NumberSeqReference_NS(_module);


//
NS end

}


throw error(strFmt(
“@SYS53742”));

}

Create a parameters table and form

You should create a parameters table and form for
your new module. The easiest way is generally to duplicate an existing
Parameters table and modify it as required.

The important elements on
the new parameter table are the numberSeqModule() and numberSeqReference()
methods.

client server static
NumberSeqModule numberSeqModule()

{

return NumberSeqReference_NS::numberSeqModule();

}

client server static
NumberSeqReference numberSeqReference()

{

return NumberSeqReference::construct(NSParameters::numberSeqModule());

}

In the parameters form, you
must ensure that the code in the numberSeqPreInit(), numberSeqPostInit() and
NumberSequenceType.executeQuery() methods correctly reflect your new number
sequence elements.

Calling a number sequence

The following code gets a
number from the number sequence setting for EDT CustAccount

static void Job1(Args _args)

{


ExtendedTypeId id = TypeID2ExtendedTypeId(TypeId(CustAccount));


NumberSeq num = NumberSeq::newGetNum(NumberSequenceReference::find(id));


;


info(num.num());

}


Upgrade from AX 3.0 to AX 2009

August 1, 2008

This topic lists the high-level steps involved in upgrading from Microsoft Dynamics AX 3.0 to Microsoft Dynamics AX 2009.

  1. Back up your existing database and application files.
  2. Import two .xpo files from the installation media to assist with data upgrade.
    • UpgradeColumnList.xpo, for 32-bit to 64-bit RecId field conversion.
    • LeftJustified.xpo, for removing any trailing spaces from fields.
      • Note: To help improve performance, you can apply the
        LeftJustified.xpo on the database that you create in step 4 after
        you’ve used the Microsoft Dynamics AX DB Upgrade Preparation tool but
        before you start the Microsoft Dynamics AX 2009 AOS. See step 8.
  3. (Optional) To help improve performance, remove all user data and
    logs of Microsoft Dynamics AX 3.0. For example, clean up the
    SysDatabaseLog table.
  4. Create an empty database for Microsoft Dynamics AX 2009 in SQL Server 2005.
  5. (Optional) To help improve performance, set initial data and log
    file sizes so that they don’t increase while you perform the data
    upgrade process.
  6. (Optional) To help improve performance, set the recovery model to Simple for the Microsoft Dynamics AX 2009 Database.
  7. Run AXDBUpgrade.exe (The Microsoft Dynamics AX DB Upgrade
    Preparation tool). Note: To help improve performance, you can run this
    tool in Multithreaded mode. For example, to run this tool in 10
    threads, enter AxDbUpgrade.exe P/10 at a command prompt.
  8. (Optional) Apply the LeftJustify file imported in step 2 to the Microsoft Dynamics AX 2009 database created in step 4.
  9. Back up your Microsoft Dynamics AX database. Your database is ready to be upgraded.
  10. Run the Microsoft Dynamics AX 2009 Setup file from the installation
    media. During installation, select the database that you created in
    step 4.
  11. Copy your upgraded customized file into the correct application directory.
  12. Start the AOS.
  13. Start the Microsoft Dynamics AX 2009 client. The Upgrade checklist is displayed automatically.
  14. Complete the steps in the Upgrade checklist to finish upgrading.

Ax Database Configuration Checklist Part 2

July 24, 2008

Tempdb database storage configuration

q Determine total size of data and transaction log required for tempdb to avoid autogrow, and number of data files required based on # of processors (logical or physical).

How:

Determine the number of processors exposed to SQL Server. Unless you are using an affinity mask (not covered here), this is the total number of processors you see in the Windows Task Manager Performance tab.

Why:

SQL Server creates one visible scheduler (for processing user requests) for each processor, and we generally want to maintain one tempdb data file per processor. This recommendation is based on performance testing on the Dynamics AX OLTP workload.

Read the rest of this entry »