用于过滤的Web API动态参数

时间:2016-05-04 11:49:20

标签: c# asp.net-web-api

我正在使用Web API构建API,我将数据库表作为JSON返回。

这是我的控制器方法:

public List<ExampleDataDTO> ExampleData(int offset = 0, int limit = 0)
{
    return DataAccessLayer.GetExampleData(offset, limit);
}

DataAccess方法:

public List<ExampleDataDTO> GetExampleData(int offset, int limit)
        {
            using (var db = new Context())
            {
                db.Configuration.ProxyCreationEnabled = false;
                var exampleQuery = db.example.AsEnumerable().Select(item =>
                 new ExampleDataDTO
                 {
                    //Selects
                 });

                if(offset == 0 && limit == 0)
                {
                    return exampleQuery .ToList();
                }
                else
                {
                    return exampleQuery .Skip(offset).Take(limit).ToList();
                }
            }
        }

现在,ExampleDataDTO包含100多个文件,我希望能够使用API​​过滤任何字段上的数据。

有没有办法制作动态查询参数?例如,如果我去localhost/api/ExampleData?offset=0&limit=10&name=test1&size=test2我想获得名称和大小的键和值,以便能够将它们包含在我的LINQ代码中。

这可行吗?

编辑:

我可以将所有参数都设为IEnumerable<KeyValuePair<String, String>> queryString

有没有办法在lambda表达式中使用Key我循环遍历所有keyValuePairs?喜欢Where(c = c.Key == Value),

foreach(var queryKeyValuePair in queryString)
{
    Something including Where(c => c.queryKeyValuePair.Key == queryKeyValuePair.Value)
}

3 个答案:

答案 0 :(得分:2)

如果我理解你的问题,你可能想要做这样的事情:

public List<ExampleDataDTO> GetExampleData(int offset = 0, int limit = 0, string name = "", string size = "")
{
    using (var db = new Context())
    {
        db.Configuration.ProxyCreationEnabled = false;
        var exampleQuery = db.example
            .Where((x => x.Name == name || name == "") && // If parameter 'name' has a value, filter on that, else ignore it.
                   (x => x.Size == size || size == "")) // If parameter 'size' has a value, filter on that, else ignore it.
            .AsEnumerable()
            .Select(item =>
            new ExampleDataDTO
            {
                //Selects
            });

        if (offset == 0 && limit == 0)
        {
            return exampleQuery.ToList();
        }
        else
        {
            return exampleQuery.Skip(offset).Take(limit).ToList();
        }
    }
}

使方法的参数可选,并在LINQ查询中使用它们进行过滤。

答案 1 :(得分:2)

您可以使用OData与WebAPI控制器动态应用:filter,orderby,select,skip,top(take)。

例如:

// your API action
public IQueryable<ExampleDataDTO> Get(ODataQueryOptions opts)
{
    // here is some odata settings to validate query params.
    var settings = new ODataValidationSettings()
    {
        // Initialize settings as needed.
        AllowedFunctions = AllowedFunctions.AllMathFunctions
    };

    // validating parameters
    opts.Validate(settings);

    var yourExampleQuery = // some data from db

    // apply all parameters that came within query to your data
    IQueryable results = opts.ApplyTo(yourExampleQuery.AsQueryable());

    // and return this
    return results as IQueryable<Product>;
}

您的查询可能类似于:http://localhost/ExampleData?$filter=SomeProperty eq 'SomeValue' $orderby=anotherProperty & $skip=10 & $top=10

所有查询参数都将解析为ODataQueryOptions并应用于您的API调用结果。

希望它会有所帮助。

答案 2 :(得分:0)

我知道该问题的答案为时已晚,但还是想分享我的回答。

我想提出一种不同的方法,那就是将逻辑转移到DB。通过这样做,您的代码将更适应更改,并且只需要在一个地方(DB)进行更改。毕竟,查询中的更改不应要求您重新编译代码。因此,这里有一个示例,说明如何实现此方法:

  1. 您应该考虑对控制器操作使用模型来收集查询的参数。这样做的好处是,如果您决定添加更多过滤器,则只需添加更多属性即可进行建模。

型号:

public class ExampleParams 
{
    public int size { get; set; } = 40; // Could be any number 
    public int page { get; set; } = 1; // you can apply offset here but this should be done on db, see below
    public string q { get; set; } = string.Empty;
}

控制器:

public List<ExampleDataDTO> ExampleData([FromQuery] ExampleParams params)
{
    return DataAccessLayer.GetExampleData(params);
}

这将使您拥有动态查询参数,例如:

localhost/api/ExampleData?q=someString将按参数q

进行搜索

localhost/api/ExampleData?q=someString&page=2将显示第2页的搜索参数q

localhost/api/ExampleData?q=someString&page=2&size=10将在第2页上显示10个搜索参数q

  1. 我假设您使用的是MySQL,但如果没有使用,则概念将保持不变。偏移量和限制可能对总体数据量不好,因此您尝试实现的分页方法可以用两种不同的方式解决。

    • 如果您知道表不会变得非常大,那么第一个将是很好的,因此可以通过上述模型中的属性“ page”轻松有效地应用offset和limit,并且limit和offset将为动态且随时随地进行计算。

      -- page and size is what you are passing via model
      SET @page := IFNULL(`page`, 1); 
      SET @size := SELECT CASE WHEN IFNULL(`size`, 40) > 40 THEN 40 ELSE IFNULL(`size`, 40) END); -- 40 is some default max limit
      
      SELECT * FROM elementTable LIMIT @size OFFSET ((@page - 1) * @size)
      
    • 如果您知道表随着时间的推移而变得很大,那么第二种方法将是更好的选择,在这种情况下,您将实现更有效的“ timestamp_id”。有关更多参考,请单击here

      -- Given that T is the timestamp and I is the id contained in the token.
      SELECT * FROM elementTable
      WHERE (
        timestampColumn > T 
        OR (timestampColumn = T AND idColumn > I)
      )
      AND timestampColumn < now()
      ORDER BY timestampColumn asc, idColumn asc;
      -- The ids in the idColumn must be unique (out-of-the-box for primary keys)
      -- We need an index on both columns timestampColumn and idColumn
      
  2. 现在,当涉及到按字段过滤时,您几乎没有选择,但是使用动态SQL进行缩放会更好,尽管编写起来很麻烦。动态SQL将为Query参数的每个排列创建并缓存一个不同的计划,但是至少每个计划都将“针对”特定的查询(无论是PROC还是Adhoc SQL,只要它们是参数化查询,它们将被缓存)

    SET @SQL = 'SELECT * FROM elementTable WHERE 1 = 1'
    IF (`optionalParam1` IS NOT NULL) then
        SET @SQL = CONCAT(@SQL , ' AND myColumn1 = \'', `optionalParam1`, '\' ' );
    IF (`optionalParam2` IS NOT NULL) then
        SET @SQL = CONCAT(@SQL , ' AND myColumn2 = \'', `optionalParam1`, '\' ' );
    
    • 如果您跟踪是否有任何谓词被使用,然后有条件地仅将第一个AND应用于第二个及后续谓词,也可以避免可怕的1 == 1骇客攻击。如果根本没有谓词,那么WHERE也将消失。

希望这可以更完整地回答您的问题。