【问题标题】:ASP.NET Web API: Perform Search on Table using HTTP GET Method and Linq to SQL DynamicallyASP.NET Web API:使用 HTTP GET 方法和 Linq to SQL 动态执行表搜索
【发布时间】:2018-01-23 18:33:26
【问题描述】:

我有一个表格来记录我希望围绕这些协议构建 Web API 的协议。我希望能够使用 GET 方法来搜索表格。但是,我希望参数是可选的。例如,如果我使用 GET 调用并指定 Date == 1/23/2018 以及 Status == Active 我希望它返回所有符合即使其他可能的参数可能等于 null。

我正在使用控制器上的绑定从需要类似协议的对象的 Uri 中提取。这只是 Table Agreement 的数据上下文。我已经能够让它工作了。问题与查询有关。如果用户只指定两个变量,则其余参数设置为空。

如何编写 linq to SQL 查询,以便只使用非空参数进行查询?解析可能的参数然后从非空值构建查询的效果。

注意:我不希望客户端必须为所有参数指定一个值。只是与它们相关的那些,然后让控制器/服务类对其进行排序。

控制器

namespace API.Controllers
{
    public class AgreementsController : ApiController
    {
        private AgreementRepository agreementRepository;

        public AgreementsController()
        {
            this.agreementRepository = new AgreementRepository();
        }

        public AGREEMENT[] Get([FromUri]AGREEMENT Agreement)
        {
            return agreementRepository.GetAgreement(Agreement);
        }
    }
}

服务

namespace API.Services
{
    public class AgreementRepository
    {
        private DatabaseEntities db = new DatabaseEntities();

        public AGREEMENT[] GetAgreement(AGREEMENT agmt)
        {
            var query = from a in db.AGREEMENTS select a;
            query.Where(a => ??????);
            AGREEMENT[] agreements = query.ToArray<AGREEMENT>();

            return agreements;
        }
    }
}

编辑 我想找到一种避免硬编码参数值的方法,并且宁愿根据可用参数动态构建查询。这样,对表和/或数据上下文的更改将直接反映并由该代码适当地处理。

编辑 2 我已经尝试使用反射来构建字典并尝试通过遍历字典来动态构建查询。下面的查询似乎无效,因为正在返回每个协议。

public AGREEMENT[] GetAgreement(AGREEMENT agmt)
{
    Type type = agmt.GetType();
    PropertyInfo[] properties = type.GetProperties();
    Dictionary<string, object> propDictionary = new Dictionary<string, object>();
    foreach (PropertyInfo property in properties)
    {
        var propertyValue = property.GetValue(agmt, null);

        if (propertyValue != null)
        {
            propDictionary.Add(property.Name, propertyValue);
        }
    }

    var query = from a in db.AGREEMENTS select a;

    foreach (var prop in propDictionary)
    {
        query.Where(A => A.GetType().GetProperty(prop.Key).GetValue(A,null) == prop.Value);
    }

    AGREEMENT[] agreements = query.ToArray<AGREEMENT>();

    return agreements;
}

【问题讨论】:

  • 如果您实际上将query.Where(...) 的结果保存回query,您的反射解决方案是否有效?
  • 您可以在运行时使用Expression 的方法创建查询。 但是请记住,在现实世界的应用程序中,每个字段的标准可能不同,例如对于字符串属性您可能需要StartsWith,对于另一个您可能需要Contains,对于一个 int 属性,您可能需要使用&gt;= 和/或&lt;= 的范围,对于另一个 int 属性,您可能需要检查确切的值等等。
  • 与其尝试编写通用逻辑,不如考虑创建特定的搜索逻辑。你可以在周围看到很好的例子,例如:Filter/Search using Multiple Fields - ASP.NET MVCFiltering the data at the controller before it is rendered in a view

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


【解决方案1】:

您可以对每个可选参数进行空检查,例如:

var query = from a in db.AGREEMENTS select a;

query = query.Where(a => a.something == mandatoryParamter);

if(optionalParameter1 != null)
{
    query = query.Where(a => a.something == optionalParameter1);
}

if(optionalParameter2 != null)
{
    query = query.Where(a => a.something == optionalParameter2);
}

等等

【讨论】:

  • 1) 这是否每次都附加到查询中或覆盖? 2)我希望有比编码每个参数(有 40 个)更干净的东西。虽然我知道这可能是一厢情愿..感谢您的意见!
  • 每次都附加到查询中。
【解决方案2】:

如果没有提供任何内容,则可以添加如下所示的 where 子句,则可选参数将为 null。

var query = from a in db.AGREEMENTS select a;

query = query.Where(a => a.something == mandatoryParamter)
    .Where(optionalParameter1 == null ||
           a => a.something == optionalParameter1)
    .Where(optionalParameter2 == null ||
           a => a.something == optionalParameter2);

【讨论】:

    【解决方案3】:

    在我看来,最简单、最强大的方法是使用 OData。 您可以在https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options开始使用

    如果您只想应用过滤器,然后以自定义方式显示和格式化结果,我会推荐上面参考中的“直接调用查询选项”部分。

    【讨论】:

      【解决方案4】:

      您可以使用 Nullable 类型运算符并使用可选参数运行查询:

          public AGREEMENT[] GetAgreement(AGREEMENT agmt)
          {
              var query = from a in db.AGREEMENTS select a;
              query.Where(a => a.col1 ?? default(col1 Type) && a.col2 ?? default(col2 Type));
              AGREEMENT[] agreements = query.ToArray<AGREEMENT>();
      
              return agreements;
          }
      

      您可以在此处添加任意数量的参数。我希望这会有所帮助。

      【讨论】:

        【解决方案5】:

        您可以使用Expression 类的方法在运行时创建查询。答案的核心逻辑是:

        1. 创建IQueryable&lt;Model&gt; result = dbContext.Models.AsQueryable();
        2. 使用查询字符串中存在的键或路由值(如果使用它们)检测哪些属性应该参与查询。
        3. 对于应包含在标准中的每个属性:

          result = result.Where(x=> create a criteria for the property);
          
        4. 最后,返回resultresult.ToList() 或任何你需要的类型作为结果。

        重要提示 - 在跳转到示例之前

        在阅读示例之前,请记住:

          1234563 @ 属性,您可能需要使用&gt;= 和/或&lt;= 的范围,对于另一个int 属性,您可能需要检查确切值等等。
        1. 与其尝试编写通用逻辑,不如考虑创建特定的搜索逻辑。你可以在周围看到很好的例子,例如:

        2. 如果属性数量和实体类数量过多,您可以做些什么来促进开发,在设计/编码时使用代码生成(例如使用 T4 模板)生成搜索逻辑。然后,您可以将它们简单地更改为您以后需要的内容。

        示例

        在以下示例中,我实现了一种搜索方法,该方法允许您根据使用查询字符串传递的值在产品之间进行搜索。您可能还想包含路由值。

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Linq.Expressions;
        using System.Net.Http;
        using System.Web.Http;
        using WebApiTest.Models;
        public class ProductsController : ApiController
        {
            [HttpGet]
            public IEnumerable<Product> Search([FromUri] Product product)
            {
                var parameters = Request.GetQueryNameValuePairs().Select(x => x.Key.ToLower());
                using (var db = new TestDBEntities())
                {
                    var result = db.Products.AsQueryable();
                    foreach (var property in typeof(Product).GetProperties())
                    {
                        if (parameters.Contains(property.Name.ToLower()))
                        {
                            var x = Expression.Parameter(typeof(Product), "x");
                            var propertyExpression = Expression.Property(x, property.Name);
                            var valueExpression = Expression.Convert(
                                Expression.Constant(property.GetValue(product)), 
                                    property.PropertyType);
                            var criteria = Expression.Equal(propertyExpression, valueExpression);
                            var lambda = Expression.Lambda<Func<Product, bool>>(criteria, x);
        
                            result = result.Where(lambda);
                        }
                    }
                    return result.ToList();
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-11-13
          • 1970-01-01
          • 1970-01-01
          • 2011-04-07
          • 2011-05-22
          • 2011-04-10
          • 2016-09-24
          相关资源
          最近更新 更多