In .NET 3.5 Microsoft released LINQ (Language INtegrated Query) – which is a way of writing select statements strongly typed inside your C# code.
Wouldn’t it be nice if we could leverage this technology when connecting to NAV Web Services?
I thought YES – so I set out to find out what it took…
The goal was to be able to replace code like this:
CustomerRef.Customer_Service cservice = new CustomerRef.Customer_Service(); cservice.UseDefaultCredentials = true;
CustomerRef.Customer_Filter filter = new CustomerRef.Customer_Filter(); filter.Field = CustomerRef.Customer_Fields.Location_Code; filter.Criteria = "=YELLOW|=BLUE"; CustomerRef.Customer_Filter[] filters = new CustomerRef.Customer_Filter[] { filter };
CustomerRef.Customer[] customers = cservice.ReadMultiple(filters, null, 0);
foreach (CustomerRef.Customer customer in customers) { // do stuff }
with code like this:
var cquery = from c in new FreddyK.NAVPageQuery<CustomerRef.Customer>(cservice) where c.Location_Code == "YELLOW" || c.Location_Code == "BLUE" select c;
foreach (CustomerRef.Customer customer in cquery) { // do stuff }
Which I personally find more readable and has a lot of other advantages:
I always heard that you only needed 3 good reasons for doing something – so this should be more than enough.
Lets look at the above code. The following
initializes the Service (as usually) and
creates a query (which contains an expression tree of the where clause) and
invokes the query (calls Web Services ReadMultiple) and returns the customers.
FreddyK.NAVPageQuery is a class, which is capable of acting as a LINQ provider for all pages exposed as Web Services.
The NAVPageQuery class is a generic class where you specify the type returned by the service – and you specify the service object as a parameter to the constructor. No need to say, that these two must match, else you will get an exception.
For a class to be usable in a LINQ query it needs to implement IQueryable<type> (which is a combination of IQueryable, IEnumerable<type> and IEnumerable) – and typically you will then have another class, which implements IQueryProvider. In my sample, I have implemented both interfaces in the NAVPageQuery.
Instead of listing the interfaces, I will list what happens when the query is build (when the line with var cquery = is executed):
Now we are done building the query and our variable cquery is set to be of type IQueryable<Customer>
When we then do a foreach on the cquery a little later, the following this happens:
You could argue that it would be a good idea to run through the expression tree while building the query – but the primary reason for NOT doing this is that you could imagine queries like:
decimal amount = 10000;
var cquery = from c in new FreddyK.NAVPageQuery<CustomerRef.Customer>(cservice) where c.Balance_LCY > amount select c;
In this case you want to separate building the query and creating the filters, since you could:
amount = 100000; foreach (CustomerRef.Customer customer in cquery) { // do other stuff }
this demonstrates the need for having the ability to decouple the query and the values used by the query and this is the reason for not creating the filter specs while building the query – but keeping the expression tree.
Another sample:
cquery = from c in new FreddyK.NAVPageQuery<CustomerRef.Customer>(cservice) where c.Last_Date_Modified >= DateTime.Today.AddDays(-5) select c;
At the time of execution, this query will give you customers modified the last 5 days (imagine building that one with filters easy)
It is important to realize that NAVPageQuery doesn’t give you any functionality you didn’t have before. With the original Filter class you cannot OR things together (unless it is the same field) – you also cannot do that using LINQ (towards NAV Web Services)
If you try to type in a where clause like
where c.Location_Code == "YELLOW" || c.Country_Region_Code == "US"
It will fail – intentionally!
Of course I could have done client side filtering – but this would only fool the developer, since you would take the performance hit when calling ReadMultiple and you wouldn’t really notice – I wanted the developer to be aware when he makes clever or not so clever choices.
So – the where clause needs to be simple enough so that I can convert it into a filter specification.
The error will be thrown when you try to use the query and it will typically be an exception stating “Query too complex” (even though it might not be complex at all).
Another thing to notice is that AND’s and OR’s cannot be combined (as this isn’t possible in the filter spec):
where c.Location_Code == "YELLOW" && (c.Balance_LCY > amount || c.Location_Code == "BLUE")
is not allowed, but
where c.Balance_LCY > amount && (c.Location_Code == "YELLOW" || c.Location_Code == "BLUE") && c.No != ""
works just fine.
Nothing new there – you still have your service object and the Customer objects that are returned from the LINQ query is the same object you would get from reading through Readmultiple directly.
This class is only designed to make queries towards web services easier and more error safe.
For once, I am not going to go through how NAVPageQuery is created in detail – I have commented the source code pretty detailed and if you need to understand how it is build, you can look at the sourcecode combined with the information on how things are executed (from above) and it should be possible to decrypt the source:-)
If you download the solution .zip file here, you will find a class library with one class (NAVPageQuery) and a Console application called TestQuery. The console app. can be deleted if you don’t want it and it has a number of samples – some of which are listed above.
When downloading the test app. – you need to update the web references (so that they match your exposed Pages) – I know that I added the Last_Date_Modified to the Customer Page in order to have access to that field through web services – there might be more.
Questions and comments are welcome, I prefer to answer questions on mibuso instead of here – since it typically makes the answer available to more people.
Enjoy
Freddy Kristiansen PM Architect Microsoft Dynamics NAV