问题
I am having an issue trying to filter data because breeze is adding the $filter clause at the end of the URL and the WCF\odata service is throwing filter cannot be after select clause.
public IQueryable<order> Orders()
{
string owner= Membership.GetUser(Thread.CurrentPrincipal.Identity.Name).owner;
IQueryable<Consigne> q = this.db.Consignes
// .AddQueryOption("Dest", dest)
.Where(x => x.Owner == owner)
.Select(f => new order{ Name= f.Name, Address1 = f.Address1, Address2 = f.Address2, Address3 = f.Address3 });
return q;
}
I'm already limiting the result set with the server side Where clause and limiting the fields with the Select projection. If I remove these and let breeze have full control of Where\Select then I blow my security model allowing the js code to have control.
I realize this isn't truly a Breeze issue and more of an odata issue but how are others dealing with this? Do you just give up on iQueryable and just create a webapi and pass back json? If so, then i'm reinventing the wheel since i also need to deal with skip\take and orderby.
Appreciate the suggestions :) Kind Regards, Mike
Solved
I've found there is no way for WCF to pass as iQueryable without loosing the TotalCount. WCF is returning a QueryOperationResponse which I can pass back to breeze, but once cast to an object by breeze there is no way I've found in Breeze's QueryHelper.WrapResults to cast the dynamic type back to a usable object to retrieve the extended TotalCount properties.
QueryHelper will execute the query
queryResult = Enumerable.ToList((dynamic)queryResult)
but
request.Properties.TryGetValue("MS_InlineCount", out tmp)
fails because of the wrong underlying object.
My solution was to Execute the query in my BreezeController and wrap the rows and TotalCount in an array as Breeze would have. Then I can pass the array back as type QueryResult and breeze will serialize to JSON to the client.
public QueryResult Consignees(string filter, int skip, int take)
{
WcfService.Context context = new WcfService.Context(new System.Uri(System.Configuration.ConfigurationManager.AppSettings["URI"]));
//Main Table
System.Data.Services.Client.DataServiceQuery<WcfService.Consigne> qMain = context.Consignes.IncludeTotalCount();
//Projected Table
System.Data.Services.Client.DataServiceQuery<Consigne> qProj = (System.Data.Services.Client.DataServiceQuery<Consigne>)qMain
.Where(x => x.Refname.StartsWith(filter))
.Skip(skip)
.Take(take)
.Select(f => new Consigne { Refname = f.Refname, Consignee = f.Consignee, Address1 = f.Address1, Address2 = f.Address2, Address3 = f.Address3 });
System.Data.Services.Client.QueryOperationResponse<Consigne> qResult= qProj.Execute() as System.Data.Services.Client.QueryOperationResponse<Consigne>;
QueryResult queryResult = new QueryResult() { Results = qResult.ToList(), InlineCount = qResult.TotalCount };
return queryResult;
}
回答1:
You have some success by passing your filter criteria as simple parameters using the Breeze .withParameters({...})
clause. You can still use orderBy
, take
, skip
.
Breeze, DTOs and the Web API
Do you need to use WCF OData? Could you switch to Web API where you have more flexibility?
Suppose we reimagine your example in the Northwind world of DocCode. For convenience, you define a DTO class
public class ProductDto { public int ProductID { get; set; } public string ProductName { get; set; } }
This is not needed, strictly speaking. But you created this class so you don't ever have to see the ugly anonymous-type-name that is generated for your projection.
Then you add a query method to the NorthwindController
like this one:
[HttpGet] public IQueryable ProductDtos() { return _repository.Products // TODO: move the following into the repository where it belongs .Where(x => x.CategoryID == 1) // a surrogate for your 'Owner' filter .Select(x => new ProductDto { ProductID = x.ProductID, ProductName = x.ProductName }); }
When your client issues a breeze query such as
var q = breeze.EntityQuery.from('ProductDtos') .where('ProductName', 'startsWith', 'C') .orderBy('ProductName') .take(5); // execute it
it resolves to the following URL
http://localhost:47595/breeze/Northwind/ProductDtos?$filter=startswith(ProductName,'C') eq true&$orderby=ProductName&$top=5
and returns four {ProductID, ProductName} objects.
If you describe ProductDto
in client-side metadata, breeze will treat these objects as entities and cache them. You'll get change notification, change tracking, validation, etc. You can save the changes back to the server where, in the beforeSaveEntities
method, you can validate them and convert them back into Product
entities so that EF can save them to the database. I'm not going into details in this answer but I want you to know that you can do it.
Restricted filtering
Note that you can only filter by the projected properties, not by properties in the undisclosed root type. For example, the following query fails because Product.SupplierID
is not among the selected ProductDto
properties:
http://localhost:47595/breeze/Northwind/ProductDtos?$filter=SupplierID eq 18&$orderby=ProductName&$top=5
The response is
Message: "The query specified in the URI is not valid.",
ExceptionMessage: "Type 'Northwind.Models.ProductDto' does not have a property 'SupplierID'.",
Given your security concerns about undisclosed properties, I assume you would want this query to fail.
But if you actually need to filter by some criteria that are not in the projected type, you could change the server method to accept a parameter. For example:
[HttpGet] public IQueryable ProductDtos(int? supplierID=null) { // TODO: move the following into the repository where it belongs var query = _repository.Products .Where(x => x.CategoryID == 1); // a surrogate for a security filter if (supplierID != null) { query = query.Where(x => x.SupplierID == supplierID); } return query.Select(x => new ProductDto { ProductID = x.ProductID, ProductName = x.ProductName }); }
Now the client can write:
var q = breeze.EntityQuery.from('ProductDtos') .withParameters( {supplierID: 18} ) // <-- passed as arg to the GET method .where('ProductName', 'startsWith', 'C') .orderBy('ProductName') .take(5); // execute it
which resolves to
http://localhost:47595/breeze/Northwind/ProductDtos?$filter=startswith(ProductName,'C') eq true&$orderby=ProductName&$top=5&supplierID=18
and the result is two ProductDto
objects that pass all filter criteria.
p.s.: I'm not making this up. I tried this code and got these results exactly as described in this answer.
来源:https://stackoverflow.com/questions/17880992/breeze-filter-projections