Friday, June 26, 2015

C# - AX AIF webservice apply company filer/field filter to existing AOT query or create query on the fly within c#

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 };

6 comments:

  1. Thanks, Adam, this is a terrific example. One question: how would one define a column relationship between the two tables for #1 above (i.e. table1.col1 = table2.col2). Many thanks.

    ReplyDelete
  2. Eric, this is a great question but one I am not 100% of the answer. I looked online and the only thing I could find how to do this is some generic info from technet. I think the best way to do this would be to use the class QueryRelationMetadata which can be applied to the class/property
    QueryDataSourceMetadata.Relations. However you could accomplish this a couple of other ways… 1.) You could define the relation on the table within the aot and using the example on this post once you call .HasRelations = true then the metadata for those tables should be pulled in automatically. 2.) Create a query and manually create the relationship on the query then use one of my previous code examples to just call the pre-defined query. If you do decided to play around with QueryDataSourceMetadata.Relations and get it working could you let me know how you ended up doing it?

    Thanks.

    Here's a couple sites on the classes I am referring to


    https://msdn.microsoft.com/en-us/library/microsoft.dynamics.ax.framework.services.metadata.contracts.querydatasourcemetadata_properties.aspx


    https://msdn.microsoft.com/en-us/library/microsoft.dynamics.ax.framework.services.metadata.contracts.queryrelationmetadata_properties.aspx

    ReplyDelete
  3. I appreciate the help. I'm continuing to try to get more information on the QueryDataSourceMetadata class and all of its methods. I'll let you know if I find anything useful. Many thanks again.

    ReplyDelete
  4. I've been communicating with Microsoft about this. Here's a link to my conversation with them and some sample code. At this point, I am still unable to join >2 tables where the result is similar to what you would get from simply entering an SQL query through Management Studio. I hope it helps.
    Link: http://partnersupport.microsoft.com/en-us/mpndynamics/forum/mpndynax/is-there-a-walkthrough-example-for-user-defined/19056a98-001f-4874-9a69-0043fa464352
    I'm not sure if you need to be an MS partner to access this, but if so, just respond and I'll post the code directly. Thanks.

    ReplyDelete
  5. Hello Eric, I have the same problem, can you send to me this code? (I'm not MS partner) My email: pospolity at gmail dot com

    ReplyDelete
  6. Hi Eric/Adam,

    I have the same problem. I have tried the above code for query service but it is not pulling existing relation on AX table by using hasrelation = true. could you please send me the sample code to make the proper relation if you get the proper solution now. My email id is : gm.281189@gmail.com

    Any help would be highly appreciated.

    ReplyDelete