Entity Framework 如何通过字符串 SQL 进行数据过滤

15

我正在将一些筛选数据存储在我的表中。让我更清楚地说明一下:我想要在数据库中存储一些where子句及其值,并在需要从数据库检索数据时使用它们。

例如,考虑一个名为people的表(实体集)和另一个表中的一些过滤器:

"age" , "> 70"
"gender" , "= male"

现在当我从people表中检索数据时,我希望使用这些过滤器来过滤我的数据。

我知道我可以生成一个字符串形式的SQL查询并执行它,但是在EF、LINQ中有更好的方法吗?


只是一个细节:我至少会将运算符放在单独的字段中。 - Raphaël Althaus
你为什么要分割整个where子句呢?如果你真的要分割,那么重构至少有可能会让人头疼。 - Paul Zahra
@PaulZahra 这可以使解析变得更容易(或者说在某种程度上避免了需要解析的需要,因为谓词已经被结构化)。请注意,您最终想要构建的是LINQ表达式树,而不是字符串。T.Rahgooy建议使用Dynamic LINQ来解析任意语言,这绝对不是一个坏主意,但是如果OP动态构建谓词(而不仅仅是一些值),那么OP将不得不诉诸于一些字符串拼接(通常不推荐)或更高级的代码生成(考虑到您可以简单地使用一流的对象来构建表达式树,这是复杂的)。 - tne
@ConductedClever 如果您希望您的问题更加通用/准确,您可以考虑重新表述为“如何使用从数据库表中检索到的动态构建的谓词来过滤数据?”但是也许您的直觉表达方式与其他人相同,这样更容易找到答案。由您决定。 - tne
@tne,据我所知,你非常聪明和机智。你对我的问题的看法甚至比我更好!非常感谢你的帮助。 - ConductedClever
显示剩余11条评论
3个回答

8

一种解决方案是使用动态Linq库,使用该库可实现以下功能:

filterTable = //some code to retrive it
var whereClause = string.Join(" AND ", filterTable.Select(x=> x.Left + x.Right));
var result = context.People.Where(whereClause).ToList(); 

假设过滤表有列LeftRight,您想通过AND连接过滤器。

我的建议是在过滤表中包含更多细节,例如将运算符与操作数分开,并添加一个确定连接是And还是OR的列以及一个确定连接此行的其他行的列。如果您想处理更复杂的查询,例如(A and B)Or(C and D),则需要树形结构。

另一种解决方案是从过滤表构建表达式树。以下是一个简单的示例:

var arg = Expression.Parameter(typeof(People));
Expression whereClause;
for(var row in filterTable)
{
     Expression rowClause;
     var left = Expression.PropertyOrField(arg, row.PropertyName);
     //here a type cast is needed for example
     //var right = Expression.Constant(int.Parse(row.Right));
     var right = Expression.Constant(row.Right, left.Member.MemberType);
     switch(row.Operator)
     {
          case "=":
              rowClause = Expression.Equal(left, right);
          break;
          case ">":
              rowClause = Expression.GreaterThan(left, right);
          break;
          case ">=":
              rowClause = Expression.GreaterThanOrEqual(left, right);
          break;
      }
      if(whereClause == null)
      {
          whereClause = rowClause;
      }
      else
      {
          whereClause = Expression.AndAlso(whereClause, rowClause);
      }
}
var lambda = Expression.Lambda<Func<People, bool>>(whereClause, arg);
context.People.Where(lambda);

这只是一个非常简化的例子,为了使它适用于所有类型的查询,您需要进行许多验证、类型转换等操作。


3
你的例子将大大帮助问题的提出者想象出需要完成的任务。让我为有兴趣的读者建议一些东西来找出他们下一步该怎么做:考虑 解释器模式访问者模式对比)。这通常会产生更易于维护的设计,但是基本思想当然是相同的。 - tne
你们两个的回答都很棒,但我更喜欢第二个。非常感谢。 - ConductedClever
不客气。第二个更具挑战性,我为你的勇气感到祝贺! - Taher Rahgooy

3

这是一个有趣的问题。首先,确保你对自己诚实:你正在创建一种新的查询语言,而这绝非易事(无论你的表达式看起来多么简单)。

如果你确定你没有低估这个任务,那么你需要看一下LINQ表达式树参考文档)。

不幸的是,这是一个相当广泛的主题,我鼓励你学习基础知识,并随着问题的出现提出更具体的问题。你的目标是解释从表中获取的过滤器表达式记录,并为它们所代表的谓词创建一个LINQ表达式树。然后,你可以像往常一样将树传递给Where()调用。


2

如果不知道您的用户界面长什么样,这里有一个简单的例子,讲述我在评论中提到的关于Serialize.Linq库的内容。

    public void QuerySerializeDeserialize()
    {
            var exp = "(User.Age > 7 AND User.FirstName == \"Daniel\") OR User.Age < 10";
            var user = Expression.Parameter(typeof (User), "User");
            var parsExpression = 
                   System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] {user}, null, exp);

            //Convert the Expression to JSON
            var query = e.ToJson();

            //Deserialize JSON back to expression
            var serializer = new ExpressionSerializer(new JsonSerializer());
            var dExp = serializer.DeserializeText(query);

            using (var context = new AppContext())
            {
                var set = context.Set<User>().Where((Expression<Func<User, bool>>) dExp);
            }

   }

你可能会使用反射和基于表达式中传入的类型调用通用LINQ查询来使它更加复杂。这样,您可以避免像我在示例末尾所做的那样强制转换表达式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接