Breeze $filter projections

雨燕双飞 提交于 2019-12-08 07:34:12

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!