Does breeze's withParameters method work with an OData service adapter? - breeze

I'm using OData with Entity Framework to select some records from a database. The records each have a path to a file with text content. In a single service call I'd like to be able to filter the DB records as well as filter the resulting objects based on the content of the files that the records point to. Because I can't mix LINQ to EF with LINQ to Objects, I believe the easiest way to accomplish this is to add an additional query parameter to the standard OData parameters that defines how to filter for file content after the standard odata filters have been applied.
Is looks like the entity query's "withParameters" method is the way to add a non-standard parameter but it doesn't seem to work with version 1.4.9 of breeze.
Am I doing something wrong or is there any intention to make this method work for the OData service provider?

As a workaround to this shortcoming, I've found that you can declare your entity to query with the parameters as part of the entity name, like so:
var entityId = 4;
var answerId = 6;
var entityToQuery = "MyEntity(EntityId=" + entityId + ",answerId=" + answerId + ")";
Then, build your breeze query:
var query = breeze.EntityQuery.from(entityToQuery);
This would map to an OData endpoint such as:
public IQueryable<MyEntity> GetMyEntity([FromODataUri] int entityId, [FromODataUri] int answerId)
{
}

No, you need to use the WebApi adapter. This is not a breeze shortcoming, it's a OData shortcoming because OData doesn't support this syntax.
However, the WebApi adapter does do everything you want and this is the Breeze default. Please see the docs for more information.

Related

Why does Odata can't parse JSON when using Extend?

I'm using OData v4 in my project Web Api (.Net Core 3.1). I'm using Devart Linqconnect to generate my model.
When I want to use "Extend" I'm getting message "can't parse JSON. Raw value".
"Select" and "Filter" working good. I'm wondering why "Extend" not working properly.
[EnableQuery(PageSize = 2)]
[HttpGet]
public IActionResult Get(ODataQueryOptions<Tasks_Object> query)
{
var items = query.ApplyTo(DBContext.Tasks_Objects.Where(i => i.Delete== null));
return Ok(items);
}
configuration of my controller/action in EDMModel.
var tasks_object = builder.EntitySet<Tasks_Object>("Task").EntityType.HasKey(e => e.ZAD_ID);
var task = builder.EntityType<Task>().HasKey(a=>a.ZAD_ID);
The problem is solved.
"Extend" not working with LinqConnect because LQ using property EntitySet<> as a relation to other table, but we can change it.
There isn't problem with EF Core because EF Core using properties as a List<> not EntityRef.

use of expand in breeze when mapping to DTOs on the server

I have had to move queries off the main database in order to meet requirements for complex authorization - for example a user with a given authorization role can only view data for individuals in the same institution.
I am using the Breeze .net DocCode sample for guidance, and have copied the premise for the mapping of domain models to DTOs.
get { return ForCurrentUser(Context.Orders).Select(o => new Order {
OrderID = o.OrderID,
....
OrderDetails = o.OrderDetails.Select(od => new OrderDetail
{
ProductID = od.ProductID,
UnitPrice = od.UnitPrice
...
})
The problem is that which mapped properties to .include(entity framework method)/.expand (breeze method) is now a concern of the mapping function (for example, the above code will always return the OrderDetails collection, whether I want them or not). I would like to still only eagerly load/expand properties if the javascript client generated predicate has a .expand directive for that property.
Is this at all possible, or am I stuck with manually defining different mapping functions on the server, depending on what properties I want expanded? (I am happy to use tools such as automapper if that would solve or simplify the problem)
Thank you
You will need to use the ODataQueryOptions as a parameter to your controller method. This gives you the details of the query predicates in your server method, so that you can apply them as needed rather that having them applied automatically. This will let you expand, or not, based upon the query.
See this answer and this answer to see how it works.

How to query on calculated column using Breeze

I have two columns in my database table (dueDate & lateMinutes).
How do I create a Where predicate that adds lateMinutes to dueDate and compares the result to a given date (aGivenDate).
eg. .where(dueDate + lateMinutes > aGivenDate)
Any idea how to do this with a Breeze query where predicate?
Any help appreciated.
Regards,
Paul.
There is no standard query syntax supporting date arithmetic within breeze, so your best bet for this would be to use a named query with a parameter. i.e. So assuming that the name of your EntityType was say 'Schedule' something like this
On the client
var q = EntityQuery.from("SchedulesAfter")
.where(...) // this can be any valid where clause for the 'Schedule' type ( or can be omitted completely if you don't need additional query restrictions.
.withParameter( { givenDate: aGivenDate });
On the server
[HttpGet]
public IQueryable<Schedule> SchedulesAfter(DateTime givenDate) {
// This needs to return a valid IQueryable
// You will need to find the correct EF syntax to support your query here. You may need to use an EF function here instead.
return ContextProvider.Context.Schedules
.Where(s => DbFunctions.AddMinutes(s.DueDate ,s.LateMinutes) > givenDate);
}
In effect, you are calling a custom query method on the server with a parameter and then telling the server how to implement the query using this parameter.

OData Web API routing

I have a web API exposing ODATA from a SQL stored proc. I want to use a url like /odata/firmhierarchy(225) to pass 225 into a param for the stored proc. It just tells me that it can't find a matching resource. It hits the controller, just skips the method. Thoughts?
In webapiconfig
private static IEdmModel GenerateEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");
builder.EntitySet<Employee>("FirmHierarchy");
return builder.GetEdmModel();
}
Context:
public virtual ObjectResult<Employee> sp_EmployeeHierarchy(Nullable<int> managerEmpID)
{
var managerEmpIDParameter = managerEmpID.HasValue ?
new SqlParameter("ManagerEmpID", managerEmpID) :
new SqlParameter("ManagerEmpID", 0);
return ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreQuery<Employee>("sp_EmployeeHierarchy #ManagerEmpID", managerEmpIDParameter);
}
Only method in controller:
[Queryable]
public IQueryable<Employee> GetFirmHierarchy()
{
return db.sp_EmployeeHierarchy(225).AsQueryable();
//return SingleResult.Create(db.Employees.Where(employee => employee.EmpId == key));
}
This should work:
1.Write another method in your controller:
[EnableQuery]
public IQueryable<Employee> Get([FromODataUri] int key)
{
return db.sp_EmployeeHierarchy(key).AsQueryable();
}
Please note that [EnableQuery] is an attribute introduced in Web API for OData V4. If you are still using Web API for OData V1-3, use [Queryable] still.
2.Then you can send the request
GET /odata/firmhierarchy(225)
and get the employees.
I was able to make ODATA work for a table, when auto-generated from entity framework. However, that generation process didn't want to work for a complex type returned by a Table Valued Function (similar scenario to a SP), because it didn't seem to understand where the key was.
What I found was that I could however make it work. First, I check out this article. He sets things up a bit more manually, where his Get on a companyProcessingController ends up routing for id 3 as "http://localhost:10020/odata/companyProcessing(3)" .
This surprised me. My other generated classes set up the pattern that SomeEntity became SomeEntityController, with methods like GetSomeEntities, and a routing that seemed to me to match the method but dropping the word get. Therefore, dropping the entity name from the Get method name seemed different, but it worked. Proving that the path is actually matching the controller name, not the method name.
In this Case you configure the routing using the data type you're querying for, and the beginning of the controller name. Then the actual path utilizes the beginning of the controller name as well.
And then all of this just brings us essentially to the other posted solution, assuming your controller name is firmhierarchyController
So, now, making sense of this... Try going to http://localhost:55063/odata/$metadata , where your port may differ. You'll notice that ODATA exposes a DataType, which is accessed via a DataSet. When a client tries to query into ODATA, they are trying to query against the DataSet, getting items of the DataType.
The DataSet matching the controller name (less Controller), and the Get methods can indeed just be Get without further extension of the name - and otherwise in this scenario was giving me problems.

Implementing $select with WebApi and ODataQueryOptions

I'm trying to implement some OData functionaility with a custom DAL using ODataQueryOptions.
My DAL uses design time generated typed data tables. By intercepting the SelectExpand property of ODataQueryOptions i can get our DAL to only load the columns required.
How do i then return just the data required.
I am currently tipping the data from our type datatables into a ListOf some typed data tranfer objects but then end up with lots of null data from the columns which aren't required.
I feel like i should be able do some LINQ query to select the just the columns I need straight from the typed datatable bypassing using typed DTOs altogether. Is this possible?
You need to do the same thing that SelectExpandQueryOption.ApplyTo does.
1) Optimize the query to the backend. Instead of getting the whole entity from the database, get only the properties the client asked for and wrap that result in an IEdmEntityObject. Return the collection as EdmEntityObjectCollection. This step is optional. You could choose to ignore this step and return IQueryable and still get $select to work.
2) Tell the OData formatter to serialize only the requested fields. This can be done by stuffing the SelectExpandClause on the Request object using the extension method Request.SetSelectExpandClause.
public class CustomersController : ODataController
{
public IEnumerable<Customer> Get(ODataQueryOptions<Customer> query)
{
Customer[] customers = new[] { new Customer { ID = 42, Name = "Raghu" } };
// Apply query
var result = customers;
// set the SelectExpandClause on the request to hint the odata formatter to
// select/expand only the fields mentioned in the SelectExpandClause.
if (query.SelectExpand != null)
{
Request.SetSelectExpandClause(query.SelectExpand.SelectExpandClause);
}
return result;
}
}

Resources