【问题标题】:Making a Generic MVC API Query进行通用 MVC API 查询
【发布时间】:2017-03-21 18:16:05
【问题描述】:

我正在 .NET Core 中构建 MVC API。我有几个非常大的表(第 3 方系统),并且我试图允许将任何列的名称作为 URL 参数传递,并在 Where 子句中使用一个值。这使用 Entity Framework 和 .NET Core。

这个想法是抓取与参数名称匹配的列,并在 Where 子句中使用它与参数的值。我希望 Where 查询中的 lambda 最终看起来像:

//GET: api/DWRWorkItems?Parameter=Value
...
dWRWorkItem = dWRWorkItem.Where(m => 
   m.Parameter == Value
);

这是我尝试一个干净的解决方案,来自控制器的路由代码。模型和视图与 Entity Framework 创建的默认值相比没有变化。

    // GET: api/DWRWorkItems
    [HttpGet]
    public IEnumerable<DWRWorkItem> GetTDwrWrkItm()
    {
        IQueryable<DWRWorkItem> dWRWorkItem = _context.TDwrWrkItm.Where(m => 1 == 1);
        var q = HttpContext.Request.Query;
        foreach (string p in q.Keys)
        {

            dWRWorkItem = dWRWorkItem.Where(m => 
                m.GetType().GetProperty(p).GetValue(m, null) == q[p]
            );

        }

        return dWRWorkItem.ToList();
    }

Intellisense (VS 2017) 不会出现错误,但是当我运行它时,我得到:

运算符“Equal”的操作数与方法“op_Equality”的参数不匹配。

单步执行代码,似乎 lambda 中的反射没有按预期工作。这是 dWRWorkItem.Expression.Arguments[1] 的样子:

{m => (GetProperty(m.GetType(), value(EBOOKS.Controllers.DWRWorkItemsController+c__DisplayClass0#1).p).GetValue(m, null) == value(EBOOKS.Controllers.DWRWorkItemsController+ c__DisplayClass0#1).CS$8__locals1.q.get_Item(value(EBOOKS.Controllers.DWRWorkItemsController+c__DisplayClass0#1).p))}

虽然参数不是动态的示例如下所示:

{m => (m.ContId == value(EBOOKS.Controllers.DWRWorkItemsController+c__DisplayClass3_0).id)}

【问题讨论】:

  • m.GetType().GetProperty(p).GetValue(m, null) 正在返回一个对象,但 q[p] 正在返回一个字符串。我希望这就是问题所在。
  • @BenJaspers 我想我的问题是:m.GetType().GetProperty(p).GetValue(m, null) // string p = "CustomString"m.CustomString 有何不同?就我而言, m.CustomString 有效,而反射方法则无效。它们是不同的类型吗?
  • 谷歌搜索您的错误消息表明问题可能是m.GetType().GetProperty(p).GetValue(m, null) 返回的对象是一个可为空的字符串,如果属性p 是一个字符串而q[p] 是不可为空的。或者我倒过来了,q[p] 可以为空,而另一个不是,文档并不完全清楚。
  • 基于反射的实现不能被翻译成 SQL,所以即使你修复了当前的异常,你也会得到另一个。您真正需要的是动态构建谓词表达式。一定有很多SO示例如何做类似的事情。
  • @BenJaspers - 遵循似乎这样说的 SO 问题链,看来这是 2013 年解决的错误:stackoverflow.com/questions/16797890/…

标签: c# entity-framework lambda .net-core


【解决方案1】:

通常,除 LINQ to Objects 之外的查询提供程序不喜欢在表达式树中调用反射,因此最好使用 System.Linq.Expressions.Expression 类方法动态组合表达式。

这是适合您的情况的自定义扩展方法:

public static partial class QueryableExtensions
{
    public static IQueryable<T> WhereEquals<T>(this IQueryable<T> source, string memberPath, string value)
    {
        var parameter = Expression.Parameter(typeof(T), "e");
        var left = memberPath.Split('.').Aggregate((Expression)parameter, Expression.PropertyOrField);
        var right = Expression.Constant(ToType(value, left.Type), left.Type);
        var predicate = Expression.Lambda<Func<T, bool>>(Expression.Equal(left, right), parameter);
        return source.Where(predicate);
    }

    private static object ToType(string value, Type type)
    {
        if (type == typeof(string)) return value;
        if (string.IsNullOrEmpty(value)) return null;
        return Convert.ChangeType(value, Nullable.GetUnderlyingType(type) ?? type);
    }
}

除了使用Expression.Equal(相当于== 运算符)之外,它还必须处理string 到实际值类型的转换,顺便说一句,这是您原来异常的原因。

用法是这样的:

var dWRWorkItem = _context.TDwrWrkItm.AsQueryable();
var q = HttpContext.Request.Query;
foreach (string p in q.Keys)
    dWRWorkItem = dWRWorkItem.WhereEquals(p, q[p]);    
return dWRWorkItem.ToList();

【讨论】:

  • 这很优雅,效果很好。我对 .NET 比较陌生,但您有什么想法为什么不是 API 的标准做法?
  • 我不知道,可能是针对一些特殊的框架 API,如 OData,但我也不确定。可能是因为他们只是通过IQueryCollection 为您提供论据,让您随心所欲地使用它们? :)
  • 好的,非常感谢。为了完整起见,在我的实现中,我将 dWRWorkItem.WhereEquals() 包装在一个 try-catch 块中,以便它优雅地处理不是列名的参数。
猜你喜欢
  • 2018-01-07
  • 2017-08-18
  • 1970-01-01
  • 1970-01-01
  • 2017-09-28
  • 2021-02-04
  • 1970-01-01
  • 1970-01-01
  • 2018-07-03
相关资源
最近更新 更多