OData $ count does not work with EntitySetController <TEntity, TKey> in web api 4
I use the EntitySetController to create the oData web api controller, and everything works fine, except to get the total number of entries.
The controller is defined as follows:
public class MyODataController : EntitySetController<Entity1, int> where TEntity : class { public override IQueryable<Entity1> Get() { return EntityDatabase.Get(); } } when I call the account through:
http://localhost:44789/oData/MyOData/$count I get the error: Invalid action detected. "$ count" is not an action that can be associated with "Collection ([Entity1 Nullable = False])".
Unfortunately, the web API does not support counting $ count out of the box, although this should be in a future version. In the meantime, you can still add support by specifying these classes:
public class CountODataRoutingConvention : EntitySetRoutingConvention { public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup<string, HttpActionDescriptor> actionMap) { if (controllerContext.Request.Method == HttpMethod.Get && odataPath.PathTemplate == "~/entityset/$count") { if (actionMap.Contains("GetCount")) { return "GetCount"; } } return null; } } public class CountODataPathHandler : DefaultODataPathHandler { protected override ODataPathSegment ParseAtEntityCollection(IEdmModel model, ODataPathSegment previous, IEdmType previousEdmType, string segment) { if (segment == "$count") { return new CountPathSegment(); } return base.ParseAtEntityCollection(model, previous, previousEdmType, segment); } } public class CountPathSegment : ODataPathSegment { public override string SegmentKind { get { return "$count"; } } public override IEdmType GetEdmType(IEdmType previousEdmType) { return EdmCoreModel.Instance.FindDeclaredType("Edm.Int32"); } public override IEdmEntitySet GetEntitySet(IEdmEntitySet previousEntitySet) { return previousEntitySet; } public override string ToString() { return "$count"; } } Register with MapODataRoute:
IList<IODataRoutingConvention> routingConventions = ODataRoutingConventions.CreateDefault(); routingConventions.Insert(0, new CountODataRoutingConvention()); config.Routes.MapODataRoute("OData", "odata", GetModel(), new CountODataPathHandler(), routingConventions); And in your controller, adding this method:
public HttpResponseMessage GetCount(ODataQueryOptions<TEntity> queryOptions) { IQueryable<TEntity> queryResults = queryOptions.ApplyTo(Get()) as IQueryable<TEntity>; int count = queryResults.Count(); HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); response.Content = new StringContent(count.ToString(), Encoding.UTF8, "text/plain"); return response; } In order not to copy GetCount () for each controller, you can define a base class that comes from the EntitySetController that defines GetCount.
One option is to use $ top = 0 in combination with $ inlinecount = allpages. A bit of a workaround, but I believe that it works great, and I'd rather return an object than a single integer. This works fine with a filter:
http://localhost:44789/oData/MyOData?$filter=MyFilter&$top=1&$inlinecount=allpages