WebApi icon indicating copy to clipboard operation
WebApi copied to clipboard

$count with Singleton returning List

Open alvorteg opened this issue 4 years ago • 8 comments

Hi, I don't know if it is a bug, or missunderstanding. I think there is little documentation on singletons and their uses.

I want to have an endpoint to get a List of conflicts (strings), so I think the best approach is to model it as Singleton.

So I've modeled it as follows:

   edmBuilder.Singleton<List<string>>("Conflicts");

And I have a Controller:

  public class ConflictsController: ODataController
    {
        [EnableQuery]
        public IActionResult Get()
        {
            return Ok(new List<string>() { "id1", "id2", "id3" }.AsQueryable()); // This is only for testing.
        }

When I access to my URL:

http://url/Conflicts

This returns a list of conflicts (strings) and this is working fine.

But now, in the Microsoft.AspNetCore.OData 7.5.5 (or above versions) if I want to do a ?$count=true to paginate those conflicts, I get the following error:

"Object of type 'System.Linq.EnumerableQuery`1[System.String]' cannot be converted to type 'System.Linq.IQueryable`1[System.Linq.IEnumerable`1[System.String]]'.",

Is this normal or is it a bug? How should it be fixed?

In the Microsoft.AspNetCore.OData 7.5.0 or 7.5.2 this worked fine... but i didn't find any related to this change on release notes.

Thanks

alvorteg avatar Mar 16 '21 14:03 alvorteg

@alvorteg That's interesting. In the Microsoft.AspNetCore.OData 7.5.0 or 7.5.2 this worked fine. do you have your repo?

If you use 8.0, you can do like:

  1. Create a POCO class, for example:
public class Conflict
{
     public IList<string> Values {get;set;}
}
  1. use this class to define a singleton:
edmBuilder.Singleton<Conflict>("Conflict");
  1. create the singleton controller.
public class ConflictController: ODataController
{
        [EnableQuery]
        public IActionResult GetValues()
        {
            return Ok(new List<string>() { "id1", "id2", "id3" }.AsQueryable()); // This is only for testing.
        }
}
  1. a request that you can use looks like: "~/Conflict/values"

xuzhg avatar Mar 17 '21 18:03 xuzhg

@xuzhg thanks for your quick response!

I had already tried what you said on the 7.5.6 version. The call works correctly and the list of strings is retrieved, but the $count=true still does not work.

I'm sorry I can't share my repo, but I can show you the obtained results with 7.5.2 and 7.5.6 versions.

With 7.5.6

image

If I add $count=true to the URL:

image

Exactly same code with 7.5.2

image

If I add $count=true to the URL:

image

Do you need some more information that can help you find the solution?

Thanks in advance!

alvorteg avatar Mar 18 '21 15:03 alvorteg

@alvorteg can you share your CSDL metadata? model builder? your controller?

xuzhg avatar Mar 22 '21 17:03 xuzhg

Startup:

public class Startup {
  public IConfiguration Configuration {
    get;
  }

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

  // This method gets called by the runtime. Use this method to add services to the container.
  public void ConfigureServices(IServiceCollection services) {
    //Persistence Injection
    services.AddPersistenceDependencyServices(this.Configuration.GetConnectionString("QueryDB"));
    services.AddDependencyInjections();

    services.AddOData();
    services.AddControllers();
  }

  // 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.UseRouting();

    app.UseEndpoints(endpoints => {
      endpoints.MapODataRoute("TestAPIv1", "TestAPI/v1", GenerateEdmModel());
      endpoints.Select().Expand().OrderBy().Filter().Count().MaxTop(null);
      endpoints.EnableDependencyInjection();
      endpoints.SetTimeZoneInfo(TimeZoneInfo.Utc);
    });
  }

  private IEdmModel GenerateEdmModel() {

    var edmBuilder = new ODataConventionModelBuilder();
    edmBuilder.Singleton<IList<string>> ("Conflicts");

    return edmBuilder.GetEdmModel();

  }
}

ConflictsController

public class ConflictsController : ODataController
    {
        private readonly IConflictsService conflictsService;

        public ConflictsController (IConflictsService conflictsService)
        {
            this.conflictsService= conflictsService;
        }

        [ODataRoute]
        [EnableQuery]
        public IQueryable<string> Get() => this.conflictsService.GetConflicts();
    }

As I mentioned in the previous message, with this code, version 7.5.2 works, and 7.5.6 doesn't work.

Thanks again @xuzhg and tell me if you need something else.

alvorteg avatar Mar 23 '21 08:03 alvorteg

@habbes Would you please help take a look? Is there any recently commit related?

@alvorteg Basically, you should not define a singleton using 'dmBuilder.Singleton<IList> ("Conflicts");`. Is there any reason that you have to do like this?

xuzhg avatar Mar 23 '21 18:03 xuzhg

@xuzhg Not really, I could create a "Conflicts" class with a property that is a list of strings (as you suggested in previous answers), but I wouldn't really need that intermediate class.

Anyway, I've tried that too and the $count=true still fails in 7.5.6.

Thanks!

alvorteg avatar Mar 24 '21 07:03 alvorteg

@habbes @xuzhg Is there any news?

alvorteg avatar Apr 21 '21 06:04 alvorteg

@alvorteg Just curious if you were able to resolve this issue? I'm having the same issue when using $orderby. My controller is named ComparablesController and it's returning IQueryable<ComparableViewModel> or IQueryable<Comparable> depending on Get method called, where these are custom object types. The IQueryable<Comparable> works fine, but the IQueryable<ComparableViewModel> throws the same conversion error you were seeing. Using Microsoft.AspNetCore.OData 8.0.10 nuget. Thanks.

mrink1074 avatar Aug 08 '22 16:08 mrink1074