AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

$Count is not working with .NET Core and ODataQueryOptions

Open asvaghela opened this issue 3 years ago • 20 comments

I am using Odata with .NET 5.0. Odata version is : (Microsoft.AspNetCore.OData\8.0.1)

$Count is not working in my each request.

http://localhost:33451/odata/Users?$count=true http://localhost:33451/odata/Users?$skip=20&$top=10&$count=true http://localhost:33451/odata/Users?$filter=Salary gt 20000 and Salary le 50000&$count=true http://localhost:33451/odata/Users?$skip=20&$top=10&$filter=Salary gt 20000 and Salary le 50000&$count=true

I am using ODataQueryOptions without EnableQuery.

I also gone through to remove "ApiController" and [Route("[controller]")] but it was not working.

OData Count Issue.zip

using Comman.TestData;
using Domain;
using Domain.BO;
using Domain.Service;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using AspNetCoreOData.Helper;

namespace AspNetCoreOData.Controllers
{
    // [ApiController]
    // [Route("[controller]")]
    public class UsersController : ControllerBase
    {
        private readonly IMapperSession session;

        // 0. Decalare constant - 2000
        private const int batchSize = 2000;

        public UsersController(IMapperSession session)
        {
            this.session = session;
        }

        [HttpGet]
        // [EnableQuery()]
        public IQueryable<User> Get(ODataQueryOptions options)
        {
            // 1. Get all IQuerable<User> from dao. 
            var userList = session.Users;

            // 2. Define Include/ExcludeIDs more than 5000
            List<int> includeIdsList = TestData.GetIncludeExcludeIdList();

            IQueryable<User> results = Enumerable.Empty<User>().AsQueryable();

            for (int i = 0; i < includeIdsList.Count; i = i + batchSize)
            {
                // 3. Split Include/ ExcludeIds in batch(constant - options.count)
                var items = includeIdsList.Skip(i).Take(batchSize).ToArray();

                IQueryable<User> getGridData = IQuerableHelper.GetGridData(userList, items, null);

                if (options.Filter != null)
                {
                    // 4.Apply Filter with Include / ExcludeIds in loop of batch.count and total includeId/ ExcludeId
                    results = results.Concat((IQueryable<User>)options.Filter.ApplyTo(getGridData, new ODataQuerySettings()));
                }
                else
                {
                    results = results.Concat(getGridData);
                }
            }

            if (options.OrderBy != null)
            {
                results = options.OrderBy.ApplyTo(results);  //perform sort 
            }

            if (options.Skip != null)
            {
                results = options.Skip.ApplyTo(results, new ODataQuerySettings());  //perform skip 
            }

            if (options.Top != null)
            {
                results = options.Top.ApplyTo(results, new ODataQuerySettings());  //perform take 
            }

            return results;
        }
    }
}


 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            var connStr = Configuration.GetConnectionString("DefaultConnection");

            services.AddNHibernate(connStr);

            //services.AddControllers()
            //    .AddOData(
            //     opt => opt.Select()
            //    .SkipToken()
            //    .SetMaxTop(100)
            //    .Count()
            //    .Filter()
            //    .AddRouteComponents("odata", GetEdmModel()));


            services.AddControllers().AddOData(opt => opt.Select().Expand().Filter().OrderBy().SetMaxTop(null).SkipToken().Count().AddRouteComponents("odata", GetEdmModel()));

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "AspNetCoreOData", Version = "v1" });
            });

            // services.AddControllersWithViews();

        }

        IEdmModel GetEdmModel()
        {
            var odataBuilder = new ODataConventionModelBuilder();

            odataBuilder.EntitySet<User>("Users");

            odataBuilder.EntitySet<User>("Employee");

            odataBuilder.EntitySet<WeatherForecast>(nameof(WeatherForecast));

            return odataBuilder.GetEdmModel();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "AspNetCoreOData v1"));
            }

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();

            });

        }
    }

Request URL : http://localhost:33451/odata/users?$skip=10&$top=10&$count=true **Response : **

{
    "@odata.context": "http://localhost:33451/odata/$metadata#Users",
    "value": [
        {
            "UserID": 7238,
            "FirstName": "adam",
            "LastName": "adkins",
            "Designation": "Business Administrator",
            "Email": "[email protected]",
            "Mobile": "1342532785",
            "Gender": "Female",
            "Salary": 19144.00,
            "CreatedAt": "2010-07-25T00:00:00+05:30"
        },
        {
            "UserID": 7239,
            "FirstName": "adolph",
            "LastName": "adams",
            "Designation": "Software Developer",
            "Email": "[email protected]",
            "Mobile": "0335938520",
            "Gender": "Male",
            "Salary": 35966.00,
            "CreatedAt": "2010-04-15T00:00:00+05:30"
        },
        {
            "UserID": 7240,
            "FirstName": "Alpesh",
            "LastName": "adkins",
            "Designation": "Frontend Developer",
            "Email": "[email protected]",
            "Mobile": "0620974648",
            "Gender": "Female",
            "Salary": 23516.00,
            "CreatedAt": "2012-02-26T00:00:00+05:30"
        },
        {
            "UserID": 7241,
            "FirstName": "abel",
            "LastName": "acosta",
            "Designation": "Technical Lead",
            "Email": "[email protected]",
            "Mobile": "0000082508",
            "Gender": "Female",
            "Salary": 36391.00,
            "CreatedAt": "2020-07-24T00:00:00+05:30"
        },
        {
            "UserID": 7242,
            "FirstName": "abe",
            "LastName": "abbott",
            "Designation": "Software Developer",
            "Email": "[email protected]",
            "Mobile": "4621393553",
            "Gender": "Male",
            "Salary": 19025.00,
            "CreatedAt": "2018-07-18T00:00:00+05:30"
        },
        {
            "UserID": 7243,
            "FirstName": "abby",
            "LastName": "adams",
            "Designation": "Manager",
            "Email": "[email protected]",
            "Mobile": "3370941413",
            "Gender": "Female",
            "Salary": 11530.00,
            "CreatedAt": "2011-02-20T00:00:00+05:30"
        },
        {
            "UserID": 7244,
            "FirstName": "adolfo",
            "LastName": "adams",
            "Designation": "Manager",
            "Email": "[email protected]",
            "Mobile": "5541084362",
            "Gender": "Female",
            "Salary": 38385.00,
            "CreatedAt": "2015-05-01T00:00:00+05:30"
        },
        {
            "UserID": 7245,
            "FirstName": "Maulik",
            "LastName": "acosta",
            "Designation": "QA",
            "Email": "[email protected]",
            "Mobile": "0574550885",
            "Gender": "Female",
            "Salary": 10403.00,
            "CreatedAt": "2020-05-01T00:00:00+05:30"
        },
        {
            "UserID": 7246,
            "FirstName": "adam",
            "LastName": "acosta",
            "Designation": "Software Developer",
            "Email": "[email protected]",
            "Mobile": "0072052077",
            "Gender": "Male",
            "Salary": 36698.00,
            "CreatedAt": "2015-03-14T00:00:00+05:30"
        },
        {
            "UserID": 7247,
            "FirstName": "Alpesh",
            "LastName": "abbott",
            "Designation": "Technical Lead",
            "Email": "[email protected]",
            "Mobile": "7722694832",
            "Gender": "Male",
            "Salary": 10209.00,
            "CreatedAt": "2013-04-20T00:00:00+05:30"
        }
    ]
}

asvaghela avatar Jul 22 '21 09:07 asvaghela

Hey @asvaghela I have looked the sample code you shared. For the UsersController, inherit from ODataController instead of the ControllerBase.

Sample code below:

public class UsersController : ODataController
{
    // Controller methods
    [EnableQuery]
    public IQueryable<User> Get()
    {
        return _db.Users.AsQueryable<User>();
    }
}

KenitoInc avatar Jul 27 '21 08:07 KenitoInc

I already tried with ODataController but the result is same.

asvaghela avatar Jul 27 '21 09:07 asvaghela

@KenitoInc @xuzhg Same issue is happening with me. We don't see the odata.Count property. Please can you let me know when this will be fixed ?

gadagrj avatar Aug 03 '21 13:08 gadagrj

Same here! only I am on dotnet 3.1 and odata 8.0.1. Using [EnableQuery] and EdmModel at startup

bturner1273 avatar Aug 04 '21 17:08 bturner1273

Same issue here. Odata v8 and .Net 5

Leonhard1987 avatar Oct 30 '21 19:10 Leonhard1987

Same here.. Any news on a way to work around? Happening also with net6

Cimbro avatar Nov 11 '21 07:11 Cimbro

Same here. Doesn't work on net6, doesn't even show @odata.context as it used to in core 3.1

rgudkov-uss avatar Dec 16 '21 18:12 rgudkov-uss

Same here. Doesn't work on net6, doesn't even show @odata.context as it used to in core 3.1

Same issue. The @odata.context is missing even in the case of OData attribute routing (using [ODataAttributeRouting] attribute) which contradicts to this article: Attribute Routing in ASP.NET Core OData 8.0 RC.

  • .Net 6
  • Microsoft.EntityFrameworkCore 6.0.1
  • Microsoft.AspNetCore.OData 8.0.4 Update: It does work but only with Edm model

buchatsky avatar Dec 24 '21 16:12 buchatsky

I managed to make it work with conventional routing instead of attribute routing. Link

rgudkov-uss avatar Jan 04 '22 10:01 rgudkov-uss

Same here. OData 8, NET 6.

FrozenPhoenix92 avatar Feb 19 '22 16:02 FrozenPhoenix92

Still seeing the same issue.

DuckScapePhilip avatar Mar 01 '22 22:03 DuckScapePhilip

@DuckScapePhilip @FrozenPhoenix92 @rgudkov-uss @Cimbro @Leonhard1987 @bturner1273 @gadagrj @asvaghela

I have a simple project with the target framework net6.0. Below are the packages I installed. Microsoft.AspNetCore.OData v8.0.8 Microsoft.EntityFrameworkCore.InMemory v6.0.3 https://github.com/KenitoInc/AspNetCoreOData8Net6

When i call GET http://localhost:5000/odata/Books?$count=true I get the response below

{
    "@odata.context": "http://localhost:5000/odata/$metadata#Books",
    "@odata.count": 9,
    "value": [
        {
            "Id": 1,
            "ISBN": "978-0-321-87758-1",
            "Title": "Essential C#5.0",
            "Author": "Mark Michaelis",
            "Price": 59.99,
            "Location": {
                "City": "Redmond",
                "Street": "156TH AVE NE",
                "Postal": {
                    "Box": "1234",
                    "PostalCode": "00100"
                }
            }
        },
        {
            "Id": 2,
            "ISBN": "063-6-920-02371-5",
            "Title": "Enterprise Games",
            "Author": "Michael Hugos",
            "Price": 49.99,
            "Location": {
                "City": "Bellevue",
                "Street": "Main ST",
                "Postal": {
                    "Box": "5554",
                    "PostalCode": "00200"
                }
            }
        },
        {
            "Id": 3,
            "ISBN": "999-6-920-02371-5",
            "Title": "Purple Hibiscus",
            "Author": "Chimamanda Ngozi",
            "Price": 49.99,
            "Location": {
                "City": "Abuja",
                "Street": "Main ST",
                "Postal": {
                    "Box": "31846",
                    "PostalCode": "00800"
                }
            }
        },
        {
            "Id": 4,
            "ISBN": "777-6-920-02371-5",
            "Title": "Things fall apart",
            "Author": "Chinua Achebe",
            "Price": 49.99,
            "Location": {
                "City": "Lagos",
                "Street": "Main ST",
                "Postal": {
                    "Box": "97984",
                    "PostalCode": "00500"
                }
            }
        },
        {
            "Id": 5,
            "ISBN": "777-6-920-02371-5",
            "Title": "Things fall apart",
            "Author": "Ngugi wa thiongo",
            "Price": 49.99,
            "Location": {
                "City": "Nairobi",
                "Street": "Moi Avenue",
                "Postal": {
                    "Box": "1314",
                    "PostalCode": "90500"
                }
            }
        },
        {
            "Id": 6,
            "ISBN": "777-6-920-02371-5",
            "Title": "Things fall apart",
            "Author": "Chinua Achebe",
            "Price": 49.99,
            "Location": {
                "City": "Lagos",
                "Street": "Main ST",
                "Postal": {
                    "Box": "97984",
                    "PostalCode": "00500"
                }
            }
        },
        {
            "Id": 7,
            "ISBN": "777-6-920-02371-5",
            "Title": "Things fall apart",
            "Author": "Chinua Achebe",
            "Price": 49.99,
            "Location": {
                "City": "Lagos",
                "Street": "Main ST",
                "Postal": {
                    "Box": "97984",
                    "PostalCode": "00500"
                }
            }
        },
        {
            "Id": 8,
            "ISBN": "777-6-920-02371-5",
            "Title": "Things fall apart",
            "Author": "Chinua Achebe",
            "Price": 49.99,
            "Location": {
                "City": "Lagos",
                "Street": "Main ST",
                "Postal": {
                    "Box": "97984",
                    "PostalCode": "00500"
                }
            }
        },
        {
            "Id": 9,
            "ISBN": "777-6-920-02371-5",
            "Title": "Things fall apart",
            "Author": "Chinua Achebe",
            "Price": 49.99,
            "Location": {
                "City": "Lagos",
                "Street": "Main ST",
                "Postal": {
                    "Box": "97984",
                    "PostalCode": "00500"
                }
            }
        }
    ]
}

when i call GET http://localhost:5000/odata/Books?$filter=Id lt 4&$count=true i get the response below

{
    "@odata.context": "http://localhost:5000/odata/$metadata#Books",
    "@odata.count": 3,
    "value": [
        {
            "Id": 1,
            "ISBN": "978-0-321-87758-1",
            "Title": "Essential C#5.0",
            "Author": "Mark Michaelis",
            "Price": 59.99,
            "Location": {
                "City": "Redmond",
                "Street": "156TH AVE NE",
                "Postal": {
                    "Box": "1234",
                    "PostalCode": "00100"
                }
            }
        },
        {
            "Id": 2,
            "ISBN": "063-6-920-02371-5",
            "Title": "Enterprise Games",
            "Author": "Michael Hugos",
            "Price": 49.99,
            "Location": {
                "City": "Bellevue",
                "Street": "Main ST",
                "Postal": {
                    "Box": "5554",
                    "PostalCode": "00200"
                }
            }
        },
        {
            "Id": 3,
            "ISBN": "999-6-920-02371-5",
            "Title": "Purple Hibiscus",
            "Author": "Chimamanda Ngozi",
            "Price": 49.99,
            "Location": {
                "City": "Abuja",
                "Street": "Main ST",
                "Postal": {
                    "Box": "31846",
                    "PostalCode": "00800"
                }
            }
        }
    ]
}

Kindly check if this sample project will help you resolve your issues

KenitoInc avatar Mar 16 '22 08:03 KenitoInc

@asvaghela Please check that you're enabling Count query option from Startup class. Something like this...

services.AddControllers().AddOData(
    options => options.Select().Filter().OrderBy().Expand().Count().SetMaxTop(null));

From a quick repro I tried, I get back @odata.count in the response. Here's the code in my controller

public class CustomersController : ODataController
{
    private static List<Customer> customers = new List<Customer>(
        Enumerable.Range(1, 3).Select(idx => new Customer { Id = idx, Name = $"Customer {idx}" }));

    public ActionResult Get(ODataQueryOptions<Customer> queryOptions)
    {
        return Ok(queryOptions.ApplyTo(customers.AsQueryable()));
    }
}

Request:

http://localhost/odata/Customers?$count=true

Response:

{
    "@odata.context": "http://localhost:52391/odata/$metadata#Customers",
    "@odata.count": 3,
    "value": [
        {
            "Id": 1,
            "Name": "Customer 1"
        },
        {
            "Id": 2,
            "Name": "Customer 2"
        },
        {
            "Id": 3,
            "Name": "Customer 3"
        }
    ]
}

gathogojr avatar Mar 17 '22 11:03 gathogojr

@asvaghela Did you get a chance to try this out?

gathogojr avatar Mar 24 '22 12:03 gathogojr

I have tried everything that is in this thread and I still have this issue.... Any new news about this problem?

danielfebres1992 avatar Jun 08 '22 16:06 danielfebres1992

I had same issue and I fixed it. It was so ridiculous. My controller was like that:

[Route("odata/[controller]")]
[Authorize(AuthenticationSchemes = TenantRequired.DefaultSchemeName)]
public class BanksController : ODataController
{
    private readonly ReadModelContext _readModelContext;
    private readonly ExcelService _excelService;

    public BanksController(ReadModelContext readModelContext, ExcelService excelService)
    
        _readModelContext = readModelContext;
        _excelService = excelService;
    }

    private IQueryable<Bank> Query() => _readModelContext.Set<Bank>();

    [EnableQuery]
    public IQueryable<Bank> GetList()
    {
        return Query();
    }
}

When I call http://localhost:8000/v2/banks?$count=true I got:

[
    {
        "bankType": null,
        "branch": "45",
        "accountNumber": "1567897",
        "cartNumber": "4444444444444",
        "shaba": null,
        "accountOwner": null,
        "initialBalance": 0,
        "detailAccountType": null,
        "code": "1001",
        "description": null,
        "firstName": null,
        "lastName": null
    },
    {
        "bankType": null,
        "branch": null,
        "accountNumber": null,
        "cartNumber": null,
        "shaba": null,
        "accountOwner": null,
        "initialBalance": 0,
        "detailAccountType": null,
        "code": "6",
        "description": null,
        "firstName": null,
        "lastName": null
    }
]

As you can see there is no @odata.count in response. So I made a little change and replace GetList with Get like this:

[EnableQuery]
public IQueryable<Bank> Get()
{
   return Query();
}

And it's worked! I Guess OData is a bit sensitive about name of actions!

{
    "@odata.context": "http://localhost:8087/odata/$metadata#Banks",
    "@odata.count": 2,
    "value": [
        {
            "detailAccountType": null,
            "code": "1001",
            "description": null,
            "firstName": null,
            "lastName": null,
            "branch": "45",
            "accountNumber": "1567897",
            "cartNumber": "4444444444444",
            "shaba": null,
            "accountOwner": null,
            "initialBalance": 0.0
        },
        {
            "detailAccountType": null,
            "code": "6",
            "description": null,
            "firstName": null,
            "lastName": null,
            "branch": null,
            "accountNumber": null,
            "cartNumber": null,
            "shaba": null,
            "accountOwner": null,
            "initialBalance": 0.0,
        }
    ]
}

And don't forget adding .Count() to `AddOData' options:

.AddOData(options => options.AddRouteComponents("odata", builder.GetEdmModel())
                .Select().Expand().Filter().OrderBy().Count().SetMaxTop(null));

I know it's so silly but it worth a try.

mhaz2000 avatar Aug 16 '22 11:08 mhaz2000

I Guess OData is a bit sensitive about name of actions!

You are absolutely right @mhaz2000 . The names used to match the route conventions are fairly strict, and that is on purpose.

Now that OData supports attribute routing, I'd recommend switching to that since it is less "magical" and gives you more flexibility.

julealgon avatar Aug 16 '22 12:08 julealgon

Have the same issue

PavelKochkin2 avatar Dec 23 '22 16:12 PavelKochkin2

Have the same issue

@PavelKochkin2 I suspect most of the issues are being caused by this bug:

  • https://github.com/OData/AspNetCoreOData/issues/430

Post your EDM model, config and controller code if you want to double check. Also check the output of the $odata debug endpoint as documented in this project's readme.

@xuzhg any chance someone on the team could prioritize the bug above? Coming from earlier versions or even migrating a non-OData API into an OData API will stumble upon that issue and it is not immediately clear what is going on.

julealgon avatar Dec 27 '22 17:12 julealgon

  1. First thing to check is Count() is added in config, like this
            .AddOData(options => options.AddRouteComponents("odata", builder.GetEdmModel())
                     .Select().Expand().Filter().OrderBy().Count().SetMaxTop(null));
  1. If you are using attribute routing, then the value used in EntitySet should match the attribute route name.
- In Config -> var odataBuilder = new ODataConventionModelBuilder();
                           odataBuilder.EntitySet<User>("Users");
                           odataBuilder.EntityType<User>().Page(maxTopValue: 100, pageSizeValue: 100);
- Attribute api route should be -> 'odata/Users'

Sundeep8051 avatar May 09 '24 12:05 Sundeep8051