Its a common ask that we introduce aggregating mechanisms in Data services so that one can do a Count of the number of entities present in an EntitySet easily. In this blog post , I will outline one method of implementing a “Count” method that works for you. The interface to the count method will be
http://<ServiceEndPoint>/Count?entitySetName='<entitySetName>'
On the server-side , we will write a Service Operation that takes the name of the EntitySet as an input parameter.
[WebGet] public long Count(string entitySetName) { //Implementation goes here }
In your Service Operations / Interceptors , you have access to the CurrentDataSource which represents your data providers. The entity sets are properties off of the CurrentDataSource. We will get the entityset off of the CurrentDataSource and get the count of entities by casting it to IListSource.
[WebGet] public long Count(string entitySetName) { long countOfEntities = 0; //Get the Property off of the CurrentDataSource PropertyInfo esProperty = this.CurrentDataSource.GetType().GetProperty(entitySetName); //Get the EntitySet off of the CurrentDataSource object esValue = esProperty.GetValue(this.CurrentDataSource, null); //Cast the EntitySet to IListSource IListSource genericESList = esValue as IListSource; //Get the count of entities by fetching the list and getting the value of its "Count" Property countOfEntities = genericESList.GetList().Count; return countOfEntities; }
we will make an extension method on the data service context that takes the name of the entityset and returns the count.
DataServiceContext nwContext = new DataServiceContext( new Uri("ServiceRoot") ); long countOfProducts = nwContext.Count("Products");
Extension method on the DataServiceContext to retrieve count of entities in entityset on the server .
public static long Count(this DataServiceContext context, string entitySetName) { //Call the ServiceOperation on the server side passing the entitySetNameas a parameter var results = context.Execute<long>(new Uri(String.Format("Count?entitySetName='{0}'", entitySetName), UriKind.RelativeOrAbsolute)); //Get the result off of the IEnumerable long count = results.First<long>(); //return the count return count; }
[WebGet] [SingleResult] public long Count(string entitySetName) { long countOfEntities = 0; if (entitySetName == null) { throw new ArgumentNullException("entitySetName"); } //Get the Property off of the CurrentDataSource PropertyInfo esProperty = this.CurrentDataSource.GetType().GetProperty(entitySetName); if (esProperty == null) { throw new ArgumentException(String.Format("No EntitySet named {0} found on the DataSource", entitySetName)); } try { //Get the EntitySet off of the CurrentDataSource object esValue = esProperty.GetValue(this.CurrentDataSource, null); //Cast the entitySet to IListSource IListSource genericESList = esValue as IListSource; //If the cast succeeded if (genericESList != null) { countOfEntities = genericESList.GetList().Count; } } catch (Exception exception) { throw new DataServiceException("'Count' method Failed with , see InnerException -->", exception); } return countOfEntities; }
public static class ClientExtensions { /// <summary> /// This Method returns the count of the number if entities present in an Entityset /// </summary> /// <param name="context">The DataServiceContext which contains the entities</param> /// <param name="entitySetName">The EntitySetName whose entities should be counted</param> /// <returns>The number of entities in the entityset</returns> /// <example> /// long countOfProducts = context.Count("Products"); /// </example> public static long Count(this DataServiceContext context, string entitySetName) { //Call the ServiceOperation on the server side passing the entitySetName as a parameter var results = context.Execute<long>( new Uri(String.Format("Count?entitySetName='{0}'", entitySetName), UriKind.RelativeOrAbsolute) ); //Get the result off of the IEnumerable long count = results.First<long>(); //return the count return count; } }
PingBack from http://informationsfunnywallpaper.cn/?p=954
Great solution if all you want is the complete count of an entityset.
But by not implementing the 'Count()' method of IQueriable in DataServices we are going to be very limited in uses for this technology. The benefits of using paging or virtual lists will be diminished because you will need to return all the results to the client to count the possible rows and discard the rows not visible in the current view.
Hi JB,
You are partially right , the ServiceOp does not take expressions as input , which means that this URI is invalid
/Count?entitySetName='Customers(1)/Orders'
You can only count top level entity sets with this function.
Now , about the second part :
"The benefits of using paging or virtual lists will be diminished because you will need to return all the results to the client to count the possible rows and discard the rows not visible in the current view. "
I dont fully understand what this means .
The support for server-side paging has existed since the CTP days in the form of $top and $skip.
If you specify the $top and $skip options why would anything other than the current page of results be returned ? Can you please clarify ?
This is how the paging functions are used in the URI :
Get First 10 Rows
/Customers?$top=10
Get 2nd page of 10 rows
/Customers?$skip=10&$top=10
The general pattern is :
/<EntitySetName>?$skip=(CurrentPageIndex*PageSize)&$top=PageSize
While not related to the intention of this post, I am wondering if the client code would remain the same if the service operation was attributed as WebInvoke (see below). I tried this and got an error 'method not allowed'. Let me know. thanks.
// SERVER
[WebInvoke]
public long Count(string entitySetName) {
//Implementation goes here
}
//CLIENT
//Call the ServiceOperation on the server side passing the entitySetName as a parameter
var results = context.Execute<long>(new Uri(String.Format("Count?entitySetName='{0}'", entitySetName), UriKind.RelativeOrAbsolute) );
Hi,
what JB (my initials are the same, btw) probably means:
How would you calculate how many pages to show when doing pagination AND filtering.
Just a simple "Displaying page 1 of 20 (filtered)" message.
Same with virtual lists, you need the row count of your query to get the scrollbar right.
Jabe,
Thanks for clarifying that .
If you are dealing with virtual rows , then there should be some actual data in the store for the same. Retrieving a count for the virtual rows in this way without downloading all the rows would be easy if you can offload the counting of the rows to the DataProvider.
When dealing with Sql Server , you can write a stored procedure and call it from the Service Operation .
or when dealing with a CLR backed data source , you can have a method on the data provider which can be accessed in the ServiceOperation via the this.CurrentDataSource property and return the virtual count for the entity set .
I would think that this would be easy to implement and gives you a lot of flexibility in customizing your counting logic.
Does that help ?
Hi, thanks for your answer.
In my case I'm using ExtJS to query my REST interface. Now, ExtJS more or less expects a total property in the JSON response. So the perfect solution for my problem would be to extend the returned JSON object and add a count.
However I am not sure if thats even possible.
Thanks for the code,
When I copy and paste your Client side code, I get a compile error on the line "long count = results.First<long>();" saying:
'System.Collections.Generic.IEnumerable<long>' does not contain a definition for 'First' and no extension method 'First' accepting a first argument of type 'System.Collections.Generic.IEnumerable<long>' could be found (are you missing a using directive or an assembly reference?)
I have both of these libraries included:
using System.Collections.Generic;
using System.Data.Services.Client;
What's missing?