Per the past previous posts of showing how to create a query within AX and calling it via the AIF in C# I ran into the dilemma of needing to add a company filter to said defined query on the fly within the C# application.
You can do this multiple ways. You can also use the examples on how to apply any filter to a query on the fly. If you are not applying filters than the static query example(s) shown in the previous post would be the way to go.
1. Create the query on the fly via code. Apply filter via code.
Pros: complete control over the structure of the query, no need for AOT object, good for small querys with only a couple tables/fields, only 1 webservice connection is needed
Cons: lots and lots of code in order to do this. The more complex the query the more code that is needed to do this, have to define every field you want to add unless you want all fields
Services needed: QueryService: http://<AOSServer>:8101/DynamicsAx/Services/QueryService
Code Example:
string axCompanyName = "<company name within AX/dataAreaId>";
DataSet dataSet;
AXQueryService.QueryMetadata query = new AXQueryService.QueryMetadata();
AXQueryService.QueryServiceClient client = new AXQueryService.QueryServiceClient();
AXQueryService.Paging paging = null;
query.DataSources = new AXQueryService.QueryDataSourceMetadata[2];
query.Name = "AXCustomerInfo";
//set up the main customer table to pull the id from
AXQueryService.QueryDataSourceMetadata custTableDS = new AXQueryService.QueryDataSourceMetadata();
custTableDS.Name = "CustTable";
custTableDS.Table = "CustTable";
custTableDS.Enabled = true;
//define the current company to search by within AX
custTableDS.Company = axCompanyName;
//define the main datasource
query.DataSources[0] = custTableDS;
//define the field listing so we dont pull in all fields
custTableDS.DynamicFieldList = false;
custTableDS.Fields = new AXQueryService.QueryDataFieldMetadata[1];
AXQueryService.QueryDataFieldMetadata accountNum;
accountNum = new AXQueryService.QueryDataFieldMetadata();
accountNum.FieldName = "AccountNum";
accountNum.SelectionField = AXQueryService.SelectionField.Database;
custTableDS.Fields[0] = accountNum;
//let the query know we need to join the table
custTableDS.HasRelations = true;
custTableDS.JoinMode = AXQueryService.JoinMode.InnerJoin;
//add the dirtparty table to the query so we can pull in the name
AXQueryService.QueryDataSourceMetadata dirPartyTableDS = new AXQueryService.QueryDataSourceMetadata();
dirPartyTableDS.Name = "DirPartyTable";
dirPartyTableDS.Table = "DirPartyTable";
dirPartyTableDS.Enabled = true;
//define the current company to search by within AX
dirPartyTableDS.Company = axCompanyName;
//define the sub datasource
query.DataSources[1] = dirPartyTableDS;
//define the field listing so we dont pull in all the fields
dirPartyTableDS.DynamicFieldList = false;
dirPartyTableDS.Fields = new AXQueryService.QueryDataFieldMetadata[1];
AXQueryService.QueryDataFieldMetadata name;
name = new AXQueryService.QueryDataFieldMetadata();
name.FieldName = "Name";
name.SelectionField = AXQueryService.SelectionField.Database;
dirPartyTableDS.Fields[0] = name;
dirPartyTableDS.HasRelations = false;
//execute the new dynamic query
dataSet = client.ExecuteQuery(query, ref paging);
2. Create AOT query object. Read in properties(meta data) from the AOT object, apply filter and run query.
Pros: complex queries can be defined and managed within the AOT, less code is needed
Cons: You need to use 2 services instead of 1 (Query & MetaData Services, both are built into default AX)
Services needed: QueryService: http://<AOSServer>:8101/DynamicsAx/Services/QueryService / Metadata Service: http://<AOSServer>:8101/DynamicsAx/Services/MetadataService
Code Example:
string axCompanyName = "<company name within AX/dataAreaId>";
DataSet dataSet;
AXMetadataService.AxMetadataServiceClient metaClient = new AXMetadataService.AxMetadataServiceClient();
AXQueryService.QueryServiceClient client = new AXQueryService.QueryServiceClient();
AXMetadataService.QueryMetadata metaQuery = metaClient.GetQueryMetadataByName(new string[] { "<aot query object name>" })[0];
AXQueryService.QueryMetadata query = new AXQueryService.QueryMetadata();
AXQueryService.Paging paging = null;
//convert the metadataservice metadata to the queryservice metadata so we can execute the query with the properties/metadata found on the aot query object
using (MemoryStream memoryStream = new MemoryStream())
{
DataContractSerializer metaSerializer = new DataContractSerializer(typeof(AXMetadataService.QueryMetadata));
DataContractSerializer querySerializer = new DataContractSerializer(typeof(AXQueryService.QueryMetadata));
metaSerializer.WriteObject(memoryStream, metaQuery);
memoryStream.Seek(0, SeekOrigin.Begin);
query = (AXQueryService.QueryMetadata)querySerializer.ReadObject(memoryStream);
}
//define the company filter based on what the user has selected
query.DataSources[0].Company = axCompanyName;
query.DataSources[0].DataSources[0].Company = axCompanyName;
//execute the query and get the results
dataSet = client.ExecuteQuery(query, ref paging);
As you can see both examples return the same results however the 2nd example is far less code. The example were using only has 2 fields that are being returned but on a more complex query that may contain 5-10 tables and more fields its pretty easy to tell that the easy way to go is to define the query within the AOT and get the properties , apply filter, execute query (2nd example)
You can also apply a filter to any range/field via the following method (this would work on both examples)
AXQueryService.QueryDataRangeMetadata range = new AXQueryService.QueryDataRangeMetadata()
{
Enabled = true,
FieldName = "dataAreaId",
Value = axCompanyName
};
query.DataSources[0].Ranges = new AXQueryService.QueryRangeMetadata[] { range };
Friday, June 26, 2015
Tuesday, June 23, 2015
C# - Creating a custom AX AIF web service that accesses logic defined within an AX AOT Class object.
In order to support legacy applications sometimes you may need to pull in data from AX into standalone applications.
Below will show you how to pull in the customer names via a custom class defined within AX but called within C#
Step 1(in AX) Create Class called CustomerInfo.
Step 2(in AX) Add method called getCustomerNameList() It should contain the following
/// <summary>
/// Get a list of customer names
/// </summary>
/// <returns>
/// List of customer names
/// </returns>
/// <remarks>
/// Used for AIF Intergration with Escalus
/// </remarks>
[SysEntryPointAttribute(true),AifCollectionTypeAttribute('return', Types::String)]
public List getCustomerNameList()
{
CustTable custTable;
DirPartyTable dirPartyTable;
List customerNames = new List(Types::String);
//select all customers and names
while select * from custTable
join dirPartyTable
where dirPartyTable.RecId == custTable.Party
{
//save the current customer
customerNames.addEnd(dirPartyTable.Name);
}
return customerNames;
}
Step 3(in AX) Create a new service called CustomerInfo. Defined the class as 'CustomerInfo' under the properties for the service.
Then right click on the 'Operations' node on the new service and defined the method (under properties) as getCustomerNameList or whatever you called the method in step 2.
Step 4(in AX) Create a new service group called Customers and include the service CustomerInfo or whatever the service was called in step 3.
Step 5(in C#) Add the following webservice to your project http://<AOSServer>:8101/DynamicsAx/Services/Customers (Customers is the service group created in step 4)
Step 6(in C#) Add the following using statement using <C# Project Class Name>.<WebService Name> example: using AXAIFTest.Customers;
Step 7(in C#) Add the following code to your load statement or a method that can be called via load
CustomerInfoClient axClient = new CustomerInfoClient();
CallContext axContext = new CallContext();
//called the method that exists within an AX class object
string[] customerList = axClient.getCustomerNameList(axContext);
CustomerNames.DataSource = customerList;
As you can see from my previous posts of http://axcalated.blogspot.com/2015/06/c-creating-aif-webservice-to-access-ax.html and http://axcalated.blogspot.com/2014/10/accessing-ax-custtable-dirpatytable.html you can access data and/or logic from within
AX multiple ways. It really just depends on where you want to write the majority your code (C#, AX/X++, or by creating aot objects within AX) and what are the requirements for the client. Personally I would recommend writing a web service to access your data
that way it could not only be implemented within C#/.NET framework languages but any language that supports accessing web services which is really just about any language these days. The only requirement as well for accessing a web service is intranet/internet access
so it leaves a small footprint on the users system along with "planning for the future" as we all know everything is moving to the web including AX 7.
C# - Creating an AX AIF webservice to access AX CustTable & DirPatyTable (Customer Info) query
In order to support legacy applications sometimes you may need to pull in data from AX into standalone applications.
Below will show you how to pull in customer id's + names (CustTable + DirPartyTable) via a webservice from a query that is defined within AX that will allow you to accomplish the same thing as http://axcalated.blogspot.com/2014/10/accessing-ax-custtable-dirpatytable.html
but without requiring the user to install AX as its a webservice so technically you could implament this solution in any language.
Step 1(in AX) Create a query called CustomerInfo. This should contain the table CustTable and sub datasource table DirPartyTable. Only include the fields CustTable.AccountNum & DirPartyTable.Name so that the footprint is as small as can be.
Its a good idea to also add an order by of DirPartyTable.Name so the results are sorted A-Z. But you can sort by number as well.
Step 2(in C#) Add the following webservice to your project http://<AOSServer>:8101/DynamicsAx/Services/QueryService (by default the port is 8101 but if you changed it during the install you would need to change it in the url as well.
This is a default/built-in webservice provided by AX and should not require you to create anything within AX in order to access it.
Step 3 (in C#) Add the following code
//this will create a container for the customer number & name
public class Customer
{
public string AccountNum { get; set; }
public string Name { get; set; }
}
add the following to the load method or create a new method that can be called from the load method
List<Customer> axList = new List<Customer>();
DataSet dataSet;
AXQueryService.QueryServiceClient client = new AXQueryService.QueryServiceClient();
AXQueryService.Paging paging = null;
//execute a static query defined with the AX AOT
dataSet = client.ExecuteStaticQuery("CustomerInfo", ref paging);
//go thru all of the results of the query and add them to your customer object list
for (int custCounter = 0; custCounter <= dataSet.Tables[0].Rows.Count - 1; ++custCounter)
{
//get the current row information for the 2 tables
DataRow custRow = dataSet.Tables["CustTable"].Rows[custCounter];
DataRow dirPartyTableRow = dataSet.Tables["DirPartyTable.DirPartyTable"].Rows[custCounter];
//create new customer entry
Customer axCustomer = new Customer()
{
AccountNum = custRow["AccountNum"].ToString(),
Name = dirPartyTableRow["Name"].ToString()
};
//add current customer to the overall list
axList.Add(axCustomer);
}
//tie our newly created customer list to your data source
CustomerNames.DataSource = axList;
CustomerNames.DisplayMember = "Name";
CustomerNames.ValueMember = "AccountNum";
CustomerIds.DataSource = axList;
CustomerIds.DisplayMember = "AccountNum";
CustomerIds.ValueMember = "AccountNum";
That's it. You should now have 2 dropdowns being displayed on the form that are linked to each other.
Below will show you how to pull in customer id's + names (CustTable + DirPartyTable) via a webservice from a query that is defined within AX that will allow you to accomplish the same thing as http://axcalated.blogspot.com/2014/10/accessing-ax-custtable-dirpatytable.html
but without requiring the user to install AX as its a webservice so technically you could implament this solution in any language.
Step 1(in AX) Create a query called CustomerInfo. This should contain the table CustTable and sub datasource table DirPartyTable. Only include the fields CustTable.AccountNum & DirPartyTable.Name so that the footprint is as small as can be.
Its a good idea to also add an order by of DirPartyTable.Name so the results are sorted A-Z. But you can sort by number as well.
Step 2(in C#) Add the following webservice to your project http://<AOSServer>:8101/DynamicsAx/Services/QueryService (by default the port is 8101 but if you changed it during the install you would need to change it in the url as well.
This is a default/built-in webservice provided by AX and should not require you to create anything within AX in order to access it.
Step 3 (in C#) Add the following code
//this will create a container for the customer number & name
public class Customer
{
public string AccountNum { get; set; }
public string Name { get; set; }
}
add the following to the load method or create a new method that can be called from the load method
List<Customer> axList = new List<Customer>();
DataSet dataSet;
AXQueryService.QueryServiceClient client = new AXQueryService.QueryServiceClient();
AXQueryService.Paging paging = null;
//execute a static query defined with the AX AOT
dataSet = client.ExecuteStaticQuery("CustomerInfo", ref paging);
//go thru all of the results of the query and add them to your customer object list
for (int custCounter = 0; custCounter <= dataSet.Tables[0].Rows.Count - 1; ++custCounter)
{
//get the current row information for the 2 tables
DataRow custRow = dataSet.Tables["CustTable"].Rows[custCounter];
DataRow dirPartyTableRow = dataSet.Tables["DirPartyTable.DirPartyTable"].Rows[custCounter];
//create new customer entry
Customer axCustomer = new Customer()
{
AccountNum = custRow["AccountNum"].ToString(),
Name = dirPartyTableRow["Name"].ToString()
};
//add current customer to the overall list
axList.Add(axCustomer);
}
//tie our newly created customer list to your data source
CustomerNames.DataSource = axList;
CustomerNames.DisplayMember = "Name";
CustomerNames.ValueMember = "AccountNum";
CustomerIds.DataSource = axList;
CustomerIds.DisplayMember = "AccountNum";
CustomerIds.ValueMember = "AccountNum";
That's it. You should now have 2 dropdowns being displayed on the form that are linked to each other.
Monday, June 22, 2015
How to get contact's email addresses from a customer or vendor
How to get customer email addresses from all contacts associated with a customer from Company/Accounts receivable/Common/Customers/All customers/Contacts/Contact Info
Method exists on \Tables\CustTable
public container getInvoiceContacts()
{
container invoiceEmailAddresses;
DirPartyTable dirParty;
LogisticsElectronicAddress electronicAddress;
DirPartyLocation dirPartyLoc;
ContactPerson contactPerson;
//find all of the customer contact email addresses that are currently active
while select * from contactPerson
where contactPerson.CustAccount == this.AccountNum && contactPerson.Inactive == NoYes::No
join RecId from dirParty
where dirParty.RecId == contactPerson.Party
join Location, Type, SendInvoice, Locator FROM electronicAddress
EXISTS JOIN Location, Party FROM dirPartyLoc
WHERE electronicAddress.Location == dirPartyLoc.Location && dirParty.RecId==dirPartyLoc.Party
&& electronicAddress.Type == LogisticsElectronicAddressMethodType::Email
{
invoiceEmailAddresses += electronicAddress.Locator;
}
return invoiceEmailAddresses;
}
How to get vendor email addresses from all contacts associated with a vendor from Company/Accounts payable/Common/Vendors/All vendors/Contacts/Contact Info
Method exists on \Tables\VendTable
public container getRemittanceContacts()
{
container remittanceEmailAddresses;
DirPartyTable dirParty;
LogisticsElectronicAddress electronicAddress;
DirPartyLocation dirPartyLoc;
ContactPerson contactPerson;
//find all of the vendor contact email addresses that are currently active
while select * from contactPerson
where contactPerson.ContactForParty == this.Party && contactPerson.Inactive == NoYes::No
join RecId from dirParty
where dirParty.RecId == contactPerson.Party
join Location, Type, SendRemittance, Locator FROM electronicAddress
EXISTS JOIN Location, Party FROM dirPartyLoc
WHERE electronicAddress.Location == dirPartyLoc.Location && dirParty.RecId==dirPartyLoc.Party
&& electronicAddress.Type == LogisticsElectronicAddressMethodType::Email
{
remittanceEmailAddresses += electronicAddress.Locator;
}
return remittanceEmailAddresses;
}
As you can see customer & vendors operate pretty much the same except for the association of custaccount(customer) vs contactforparty(vendor) on the contactperson table.
I'm posting this since AX seems to love to use the view "DirPartyPostalAddressView" as the main listing for these contacts on the form. So it can take a little while of digging to discover the true relationship between these tables.
Method exists on \Tables\CustTable
public container getInvoiceContacts()
{
container invoiceEmailAddresses;
DirPartyTable dirParty;
LogisticsElectronicAddress electronicAddress;
DirPartyLocation dirPartyLoc;
ContactPerson contactPerson;
//find all of the customer contact email addresses that are currently active
while select * from contactPerson
where contactPerson.CustAccount == this.AccountNum && contactPerson.Inactive == NoYes::No
join RecId from dirParty
where dirParty.RecId == contactPerson.Party
join Location, Type, SendInvoice, Locator FROM electronicAddress
EXISTS JOIN Location, Party FROM dirPartyLoc
WHERE electronicAddress.Location == dirPartyLoc.Location && dirParty.RecId==dirPartyLoc.Party
&& electronicAddress.Type == LogisticsElectronicAddressMethodType::Email
{
invoiceEmailAddresses += electronicAddress.Locator;
}
return invoiceEmailAddresses;
}
How to get vendor email addresses from all contacts associated with a vendor from Company/Accounts payable/Common/Vendors/All vendors/Contacts/Contact Info
Method exists on \Tables\VendTable
public container getRemittanceContacts()
{
container remittanceEmailAddresses;
DirPartyTable dirParty;
LogisticsElectronicAddress electronicAddress;
DirPartyLocation dirPartyLoc;
ContactPerson contactPerson;
//find all of the vendor contact email addresses that are currently active
while select * from contactPerson
where contactPerson.ContactForParty == this.Party && contactPerson.Inactive == NoYes::No
join RecId from dirParty
where dirParty.RecId == contactPerson.Party
join Location, Type, SendRemittance, Locator FROM electronicAddress
EXISTS JOIN Location, Party FROM dirPartyLoc
WHERE electronicAddress.Location == dirPartyLoc.Location && dirParty.RecId==dirPartyLoc.Party
&& electronicAddress.Type == LogisticsElectronicAddressMethodType::Email
{
remittanceEmailAddresses += electronicAddress.Locator;
}
return remittanceEmailAddresses;
}
As you can see customer & vendors operate pretty much the same except for the association of custaccount(customer) vs contactforparty(vendor) on the contactperson table.
I'm posting this since AX seems to love to use the view "DirPartyPostalAddressView" as the main listing for these contacts on the form. So it can take a little while of digging to discover the true relationship between these tables.
Monday, June 8, 2015
Unable to save. Version of [object] on the server is newer
I seem to be getting the following message on a very specific object whenever I check it out and make a modification. Unable to save. Version of <object> on the server is newer.
You usually get this message whenever someone else has modified the object since you opened MorphX/AX. However I was the one who checked this out and have tried closing out of AX completely and I would still get the message and I know no one else is accessing this object.
I found that sometimes it would allow me to save the change but then would never show it in the Client. I would restart the client and check the AOT object but it would not have saved my change made. If I exported the object from a different environment and imported into the dev environment (where is the issue was happening) then everything from the import would save ok but it would still not allow me to manually make changes.
In order to fix this error right click (in MorphX/AOT) on the object and choose "Restore" This doesn't seem to discharge my changes but now whenever I add something to this object it seems to save it to the DB and displays correctly in the client.
According to https://msdn.microsoft.com/en-us/library/aa846291.aspx this shortcut "Reads the selected element from the database and discards the changes that were made since the last save."
You usually get this message whenever someone else has modified the object since you opened MorphX/AX. However I was the one who checked this out and have tried closing out of AX completely and I would still get the message and I know no one else is accessing this object.
I found that sometimes it would allow me to save the change but then would never show it in the Client. I would restart the client and check the AOT object but it would not have saved my change made. If I exported the object from a different environment and imported into the dev environment (where is the issue was happening) then everything from the import would save ok but it would still not allow me to manually make changes.
In order to fix this error right click (in MorphX/AOT) on the object and choose "Restore" This doesn't seem to discharge my changes but now whenever I add something to this object it seems to save it to the DB and displays correctly in the client.
According to https://msdn.microsoft.com/en-us/library/aa846291.aspx this shortcut "Reads the selected element from the database and discards the changes that were made since the last save."
Subscribe to:
Posts (Atom)