为什么我不能在IEnumerables上构建动态查询?

本文关键字:构建 动态 查询 IEnumerables 不能 为什么 | 更新日期: 2023-09-27 18:18:48

我想在运行时由用户提供的列上订购一个IEnumerable。要动态地构建LINQ,我知道我可以:

  1. 写切换用例语句执行各种用例;
  2. 构建表达式树并将其传递为xyz.OrderBy(myExpression.Compile());
  3. 通过调用xyz.AsQueryable()IEnumerable管道化为IQueryable,然后再执行.OrderBy(userInputString);

关于上面第三种方法的简单问题-为什么 IEnumerables不直接提供动态查询构建能力?

为什么我不能在IEnumerables上构建动态查询?

这样做几乎没有什么好处,而有许多缺点。在任何情况下,您都需要(或库需要)能够将字符串转换为一组指令,说明要做什么,并且您需要能够解析字符串以确保它们没有要求您不希望它做的事情。大多数程序员(尤其是新手)不了解他们所做的工作的安全后果,因此他们在不知不觉中编写了不安全的代码。没有这样的库迫使程序员实际解析输入,而他们无论如何都应该这样做。

一个典型的讨论可能是:为什么我们不能至少按顺序传递对象的属性名称呢?好的。那么为什么我们不能通过传递属性的名称来选择呢?好的。那么为什么我们不能通过传递像prop=="this"这样的字符串表达式来过滤呢?然后突然之间,我们到处都有安全问题,因为程序员懒得检查用户是否提供了"或"1"="1"作为他们的输入。 所以程序员开始写这样的代码:
var usercheckstring="Username='"+txtUser.Text+"' and Password='"+txtPassword.Text+"'";
var user=db.Users.Where(usercheckstring).FirstOrDefault();
if (user!=null)
{
   ... Log in the user here ...
}
当一些黑客输入' or Admin='1作为密码时,它会以管理员的身份登录。然后人们说c#或LINQ是不安全的,其实这总是程序员的错。

虽然Scott Gu是一个好人,几乎无可争议是一个伟大的程序员,但即使他也陷入了同样的陷阱,并在他的DynamicLinq博客上给出了一些令人难以置信的糟糕建议:http://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library,他说你可以这样做:

.Where(String.Format("CategoryID={0}" & Request.QueryString["id"]) 

哇!当然,你可以这样做,但你绝对不应该这样做(除了它有一个语法错误)。坏斯科特,坏!他只是向世界展示了如何进行动态linq注入。虽然没有典型的SQL注入攻击那么严重,但它遭受的攻击方式是一样的。


盲目使用用户提供的数据总是一个坏主意,这是99%的人想用动态LINQ做的事情(我要么在网上找到这个统计数据,要么我自己编的)。他们不知道如何用普通的LINQ做到这一点,想要走一条糟糕的捷径,然后被烧死。switch/case并没有那么难,只需要几行代码,并不不安全,并且比在对象上进行反射以尝试将字符串转换为lambda要快得多。

我从ScottGu的博客上提供的动态查询库中导入了一个动态查询类到我的项目中。这为我提供了扩展方法,通过在运行时仅提供字段名来排序集合,从而构建动态查询。使用这个库,我可以做如下事情:

var employees = new List<Employee>
{
    new Employee
    {
        DeptId = 1,
        Id = 1,
        Name = "JKL"
    },
    new Employee
    {
        DeptId = 1,
        Id = 2,
        Name = "ABC"
    },
    new Employee
    {
        DeptId =  5,
        Id = 3,
        Name = "LMN"
    }
};
var query1 = employees.OrderBy(e => e.Name);
foreach (var employee in query)
{
    Console.WriteLine(employee.Name); //Prints names
}
var query2 = employees
    .AsQueryable()
    .OrderBy("Name");
foreach (var employee in query2)
{
    Console.WriteLine(employee.Name); //Prints exact output as foreach loop above.
}

请注意,虽然在query1中我必须在.OrderBy()中编写lambda,但我只在query2中直接提供字段名称,但我需要在集合上做.AsQueryable()

雇员定义为:

public class Employee
{
    public int Id;
    public string Name;
    public int DeptId;
}

所以,这个问题的答案是,如果我想知道为什么不能直接在IEnumerable上做OrderBy("Name"),我应该发送邮件并询问ScottGu或阅读源代码以获取有关相同的提示。