Unexpected result with $select statement using WebApi 2 - asp.net-mvc

I am working on a project using MVC 4 and WebApi 1 and it work well,
After I upgraded it to MVC 5 and WebApi 2, everything works great, except this scenario: when i use the $select statement in the query and specify some properties, the options.ApplyTo() method returns unexpected results:
System.Web.Http.OData.Query.TruncatedCollection`1[System.Web.Http.OData.Query.Expressions.SelectExpandBinder+SelectSome`1[MvcApp.Domain.Entities.Users]]
Why this change, and how to convert it to IQueryable< Users> as WebApi 1?
my code here:
public PageResult<Users> Get(ODataQueryOptions options)
{
ODataQuerySettings settings = new ODataQuerySettings() { PageSize = 10 };
IQueryable results = options.ApplyTo(Repository.Data, settings);
var items = results as IEnumarable<Users>;
// items will be null if you $select some properties!
return new PageResult<Users>(items, null, Request.GetInlineCount().Value);
}
Help please.

Related

ASP.NET Core 6 ODATA Count not returning a value

I have recently upgraded to .NET 6 Core Preview and I am experiencing some issues with ODATA. What use to work in .NET Core 3.1 no longer works.
Here is my Startup snippet:
I also believe EnableQueryFeatures() allows for Count by default.
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
}).AddOData(opt => opt
.EnableQueryFeatures()
.AddRouteComponents("odata", GetEdmModel()
);
I have the following Action Method and when called, the count is not returned
to the front end.
The pageResult holds a count value, but is not being returned to the front end,
when I inspect the result using Chrome DevTools.
[Route("odata/Tasks")]
public PageResult<TaskModel> AjaxListOData([FromQuery]ODataQueryOptions queryOptions)
{
var result = queryOptions.ApplyTo(dataContext.Tasks) as IEnumerable<TaskModel>;
var pageResult = new PageResult<TaskModel>(
result,
null,
count: Request.ODataFeature().TotalCount);
return pageResult;
}
I resolved the problem by inheriting from ODataController and not from
Controller.

BreezeJs: Error: Unable to convert this endpoint to an IQueryable

I have asp.net core 1.1.0 project and trying the model of CodeCamp sample. In this we a controller which return Lookup data as below :
[BreezeController]
public class BreezeController : ApiController
{
[HttpGet]
public object Lookups()
{
var rooms = _repository.Rooms;
var tracks = _repository.Tracks;
var timeslots = _repository.TimeSlots;
return new { rooms, tracks, timeslots };
}
And the above Lookups is called in dataContext.js as below:
function getLookups() {
return EntityQuery.from('Lookups')
.using(manager).execute()
.to$q(querySucceeded, _queryFailed);
function querySucceeded(data) {
log('Retrieved [Lookups]', data, true);
return true;
}
}
Now, I am trying to follow same as above in my project its giving me error as below :
Get http://Localhost:12345//breeze/demo/Lookups 500(Internal server error)
Uncaught (in promise)
Error: Unable to convert this endpoint to an IQueryable
Any solution to above issue...its working fine in John Papa's Code camper project. My web api lookups code is working fine if I run it in browser but not with breezejs.
Breeze's .NET Core implementation expects a hidden first parameter. It uses this to perform the IQueryable filtering of a REST operation. For example, if you have an operation that looks like this:
[HttpGet]
public IQueryable<Order> Orders()
And you wanted to get all Orders with the Status of 123 that Cost less than $10 then the first parameter would be something like this:
{
"where":{
"Status":123,
"Cost":{
"lt":10
}
},
"select":[
"OrderId"
]
}
This is a significant departure from the previous version. The client can be changed to pass parameters compatable with this by adding:
breeze.config.initializeAdapterInstance("uriBuilder", "json");
I added this to my fetchMetadata call.
However, this causes a lot of problems if you have specific get methods with parameters and you want to call it from Swagger or another application.
Something Like this:
[HttpGet]
public IQueryable<Order> GetOrdersByStatusAndLessThanCost(int status, int cost)
Will generate a url like this:
GetOrdersByStatusAndLessThanCost?status=123&cost=10
Breeze assumes that the first parameter (status=123) is its JSON. So it tries to parse it out.
This gives the first most common error with migrating Breeze to .NET Core:
This EntityQuery ctor requires a valid json string. The following is not json: status=123
If you happened to pass in Json, but the result is not an IQueryable, then you will get this error:
Unable to convert this endpoint to an IQueryable
The key to all of this is to give breeze what it is looking for. For the example above the following URL would work:
GetOrdersByStatusAndLessThanCost?{}&status=123&cost=10
Note the added {}& as the first parameter. This tells breeze that there is not anything expected as far as filtering goes.
To get this working for Swashbuckle (and by extension Swagger\Open API) add this to your Startup.cs ConfigureServices method inside the call to services.AddSwaggerGen(c =>:
c.OperationFilter<AddBreezeParameter>();
And then create the file that is needed for that:
public class AddBreezeParameter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (context.MethodInfo.ReturnType.Name.StartsWith("IQueryable"))
{
if (operation.Parameters == null)
{
operation.Parameters = new List<OpenApiParameter>();
}
var exampleString = "<br>\":{}," +
"<br> \"where\":{" +
"<br> \"Status\":123," +
"<br> \"Cost\":{" +
"<br> \"lt\":10" +
"<br> }" +
"<br> }," +
"<br> \"select\":[" +
"<br> \"OrderId\"," +
"<br> \"OrderDateTime\"" +
"<br> ]" +
"<br>}";
var breezeJsonParam = new OpenApiParameter
{
Name = "{\"breezeJson",
In = ParameterLocation.Query,
AllowEmptyValue = true,
Description =
"Json used to query a REST resource. <br>Due to Breeze's nonstandardness and Swashbuckle's not able to customize to allow for it, this MUST start with \":{} and end with } In between those you can put your query if it is appropriate. If you do you must add a comma after the starting value and before you value. Here is an example: " +
exampleString,
AllowReserved = true
};
var schema = new OpenApiSchema {Type = "json", Default = new OpenApiString("\":{}}")};
breezeJsonParam.Schema = schema;
operation.Parameters.Insert(0, breezeJsonParam);
}
else
{
if (operation.Parameters == null)
{
operation.Parameters = new List<OpenApiParameter>();
}
var breezeJsonParam = new OpenApiParameter();
// Breeze looks for the first parameter so it can do an IQueryable Filter on it.
// We want it to not have anything for that parameter if it is not an IQueryable.
breezeJsonParam.Name = "{}&";
breezeJsonParam.In = ParameterLocation.Query;
breezeJsonParam.Description = "Do NOT modify this parameter. (It is here for Breeze compatibility.)";
var schema = new OpenApiSchema {Example = new OpenApiString(" ")};
//var schema = new OpenApiSchema {Type = "string", Default = new OpenApiString("\":{}}")};
breezeJsonParam.Schema = schema;
operation.Parameters.Insert(0, breezeJsonParam);
}
}
}

Selecting only part of the DbSet in Read method of Kendo grid

I'm trying to use a Kendo UI grid in MVC and remote data. I want to only grab and display data from the DbSet, onload, where one of the fields, "Status", equals '1'. I thought this should be able to be accomplished in the controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
using (var db = new MyData(false))
{
var data = db.Training.Where(d => d.Status == '1').Select(d => new Training {
Id = d.Id,
Name = d.Name,
Status = d.Status
}).ToDataSourceResult(request);
return Json(data, JsonRequestBehavior.AllowGet);
}
}
The above code gives me the error that "The entity or complex type 'Training' cannot be constructed in a LINQ to Entities query". Any suggestions on how to rewrite the Linq statement so it'll work, or maybe a way to do it within the grid to suppress any that do not have a Status of '1'?
Your code is trying to project to a mapped entity which is not allowed. Additionally it's redundant to do that as you already have your entities. Remember that .Select() is for mapping one type to another but the .Where() method is already returning a list of your entities (Training).
Remove the .Select() and the query should work:
var data = db.Training.Where(d => d.Status == '1').ToDataSourceResult(request);

Value cannot be null error in processing OData from Web API

I have a WebAPI method that returns OData:
[HttpGet]
public PageResult<Students> GetStudents(ODataQueryOptions<Students> queryOptions, string ClassId)
{
var allStudents = (from s in new OStudentContext(Convert.ToInt64(ClassId)).Student select s).ToList();
var results = queryOptions.ApplyTo(allStudents.AsQueryable());
return new PageResult<Students>(results as IEnumerable<Students>, Request.GetNextPageLink(), Request.GetInlineCount());
}
The request URL is like so:
http://localhost:3333/api/odata/GetStudents?StudentId=40932&$inlinecount=allpages&$filter=((IsDeleted%20eq%20null)or(IsDeleted%20eq%20false))&$select=StudentId,FirstName,LastName,EmailID
The value in results is there and I can see the records returned. The count is actually 7 to be precise.
The problem is the the return statement throws this exception:
Value cannot be null.Parameter name: data
Request.GetNextPageLink() is null as there is no next page link.
Request.GetInlineCount() is 7.
So what is null & what does the error mean about data?
Thanks in advance.
The element type after applying ODataQueryOptions is no longer Students if there is some $select or $expand.
It changes to SelectExpandWrapper instead.
So the result can't be cast to IEnumerable<Students>.
For the structure of SelectExpandWrapper, you can refer to https://aspnetwebstack.codeplex.com/wikipage?title=%24select%20and%20%24expand%20support

Linq Generic OrderBy selector

I am using the repository pattern in a asp.net mvc application (v3) and EntityFramework (v4).
I would like to add to the repository paging functionality. I have the following sample code:
public PagedResult<T> GetPaged(int offset = 0, int limit = PagedResult<T>.NoLimit, Expression<Func<T, bool>> predicate = null)
{
var res = BaseQ.Where(_predicate);//.Skip(offset);
if (predicate != null)
res = res.Where(predicate);
res = res.Skip(offset);
if (limit != PagedResult<T>.NoLimit)
{
res = res.Take(limit);
}
return new PagedResult<T>(res, res.Count(), offset, limit);
}
However, this will not work because Entity framework throws an exception that I should call OrderBy before Skip.
I am wondering how can I implement this in a generic way so that the order by expression will be plugged in from the outside. I would then pass it as a parameter.
Perhaps there is also another way of working around that problem.
[EDIT] I found out that this can be done by passing in the name of the property and creating an expression out of that, but I would like to actually just use it as I am using it in the OrderBy. So just pass in (c=>c.ID) for example.
Thanks in advance for any ideas
I have decided to go with passing in a string and creating the expression from it

Resources