简体   繁体   English

具有多个/未知条件的动态 linq 查询

[英]Dynamic linq query with multiple/unknown criteria

I am looking to implement a system whereby a use that 'build' conditions and then return the resulting data back from the database.我正在寻求实现一个系统,该系统使用“构建”条件,然后从数据库返回结果数据。 At present, there is a stored procedure which generates SQL on the fly and executes it.目前有一个存储过程动态生成SQL并执行。 This is a particular issue that I want to remove.这是我要删除的特定问题。

My problem is coming from the fact that I can have multiple fields within my criteria, and for each of these fields, there could be 1 or more values, with different potential operators.我的问题来自这样一个事实,即我的条件中可以有多个字段,并且对于每个字段,可能有 1 个或多个值,具有不同的潜在运算符。

For example,例如,

from t in Contacts 
where t.Email == "email@domain.com" || t.Email.Contains ("mydomain")
where t.Field1 == "valuewewant"
where t.Field2 != "valuewedontwant"
select t

The field, criteria and operator are stored in the database (and List<FieldCriteria> ) and would be some thing like this (based on above);字段、条件和运算符存储在数据库中(和List<FieldCriteria> ),并且会是这样的(基于上述);

Email, Equals, "email@domain.com"
Email, Contains, "mydomain" Field1,
Equals, "valuewewant" Field2,
DoesNotEqual, "valuewedontwant"

or或者

new FieldCriteria
{
FieldName = "Email",
Operator = 1, 
Value = "email@mydomain.com"
}

So using the information that I have, I want to be able to build a query with any number of conditions.因此,使用我拥有的信息,我希望能够构建具有任意数量条件的查询。 I have seen previous links to Dynamic Linq and PredicateBuilder, but am not able to visualise this as a solution to my own problem.我已经看到了 Dynamic Linq 和 PredicateBuilder 的先前链接,但我无法将其可视化为我自己问题的解决方案。

Any suggestions would be appreciated.任何建议,将不胜感激。

Update更新

Following on from the suggestion about Dynamic Linq, I came up with a very basic solution, using a Single Operator, with 2 Fields and multiple Criteria.根据有关动态 Linq 的建议,我提出了一个非常基本的解决方案,使用单个运算符,具有 2 个字段和多个条件。 A little crude at the moment as coded in LinqPad, but the results are exactly what I wanted;目前在 LinqPad 中编码有点粗糙,但结果正是我想要的;

enum Operator
{
    Equals = 1,
}

class Condition
{
    public string Field { get; set; }
    public Operator Operator { get; set;}
    public string Value { get; set;}
}

void Main()
{
    var conditions = new List<Condition>();

    conditions.Add(new Condition {
        Field = "Email",
        Operator = Operator.Equals,
        Value = "email1@domain.com"
    });

    conditions.Add(new Condition {
        Field = "Email",
        Operator = Operator.Equals,
        Value = "email2@domain.com"
    });

    conditions.Add(new Condition {
        Field = "Field1",
        Operator = Operator.Equals,
        Value = "Chris"
    });

    var statusConditions = "Status = 1";

    var emailConditions = from c in conditions where c.Field == "Email" select c;
    var field1Conditions = from c in conditions where c.Field == "Field1" select c;


    var emailConditionsFormatted = from c in emailConditions select string.Format("Email=\"{0}\"", c.Value);
    var field1ConditionsFormatted = from c in field1Conditions select string.Format("Field1=\"{0}\"", c.Value);

    string[] conditionsArray = emailConditionsFormatted.ToArray();
    var emailConditionsJoined = string.Join("||", conditionsArray);
    Console.WriteLine(String.Format("Formatted Condition For Email: {0}",emailConditionsJoined));

    conditionsArray = field1ConditionsFormatted.ToArray();
    var field1ConditionsJoined = string.Join("||", conditionsArray);
    Console.WriteLine(String.Format("Formatted Condition For Field1: {0}",field1ConditionsJoined));



    IQueryable results = ContactView.Where(statusConditions);

    if (emailConditions != null)
    {
        results = results.Where(emailConditionsJoined);
    }

    if (field1Conditions != null)
    {
        results = results.Where(field1ConditionsJoined);
    }

    results = results.Select("id");

    foreach (int id in results)
    {
        Console.WriteLine(id.ToString());
    }
}

With an SQL generated of;用 SQL 生成的;

-- Region Parameters
DECLARE @p0 VarChar(1000) = 'Chris'
DECLARE @p1 VarChar(1000) = 'email1@domain.com'
DECLARE @p2 VarChar(1000) = 'email2@domain.com'
DECLARE @p3 Int = 1
-- EndRegion
SELECT [t0].[id]
FROM [Contacts].[ContactView] AS [t0]
WHERE ([t0].[field1] = @p0) AND (([t0].[email] = @p1) OR ([t0].[email] = @p2)) AND ([t0].[status] = @p3)

And Console Output:和控制台 Output:

Formatted Condition For Email: Email="email1@domain.com"||Email="email2@domain.com"
Formatted Condition For Field1: Field1="Chris"

Just need clean this up and add the other Operators and it is looking good.只需要清理它并添加其他操作员,它看起来不错。

If anyone has any comments on this so far, any input would be appreciated到目前为止,如果有人对此有任何意见,我们将不胜感激

The trick with LINQ would be to build an Expression from the data. LINQ 的诀窍是根据数据构建Expression As an example, to illustrate the example shown:作为一个例子,为了说明显示的例子:

var param = Expression.Parameter(typeof(MyObject), "t");

var body = Expression.Or(
            Expression.Equal(Expression.PropertyOrField(param, "Email"), Expression.Constant("email@domain.com")),
            Expression.Call(Expression.PropertyOrField(param, "Email"), "Contains", null, Expression.Constant("mydomain"))
        );

body = Expression.AndAlso(body, Expression.Equal(Expression.PropertyOrField(param, "Field1"), Expression.Constant("valuewewant")));
body = Expression.AndAlso(body, Expression.NotEqual(Expression.PropertyOrField(param, "Field2"), Expression.Constant("valuewedontwant")));

var lambda = Expression.Lambda<Func<MyObject, bool>>(body, param);

var data = source.Where(lambda);

In particular, note how AndAlso can be used to compose the various operations (the same as multiple Where , but simpler).特别要注意AndAlso是如何用于组合各种操作的(与多个Where相同,但更简单)。

I think Dynamic LINQ will be one of option.我认为动态 LINQ 将是一种选择。 DLINQ allows you to specify part of the LINQ query as "string" and DLINQ then compiles that string to Expression tree so that be passed to the underlying LINQ provider. DLINQ 允许您将 LINQ 查询的一部分指定为“字符串”,然后 LINQ 将该字符串编译为表达式树,以便传递给底层 LINQ 提供程序。 Your need is also same ie you need to create Expression trees at runtime.您的需求也相同,即您需要在运行时创建表达式树。

I would suggest you to make the property Operator in FieldCriteria as an Enum which represent all the required operations (equals, less then etc).我建议您将FieldCriteria的属性Operator FieldCriteria Enum ,代表所有必需的操作(等于、小于等)。 Then you will need to write a function that takes a list of FieldCriteria and return a "expression" string which then can be fed into DLINQ to get the expression tree.然后您将需要编写一个函数,该函数接受一个FieldCriteria列表并返回一个“表达式”字符串,然后可以将其输入 LINQ 以获取表达式树。

This sounds very similar to a problem I solved recently.这听起来与我最近解决的一个问题非常相似。 In my case, I had to filter the objects into different categories based on a complex filters that were defined in Sql.就我而言,我必须根据 Sql 中定义的复杂过滤器将对象过滤到不同的类别。

I have created a Nuget package DynamicFilter.Sql to dynamically generate the lambda expression from a sql based filter. I have created a Nuget package DynamicFilter.Sql to dynamically generate the lambda expression from a sql based filter. The package is open source and available on github . package 是开源的,可在github上获得。

you can simply use it like so,你可以像这样简单地使用它,

var filter = FilterExpression.Compile<User>("(Email = 'email@domain.com' or Email like '%@%mydomain.com') and deleted <> true ");


bool match = filter(new User {Email="alice@mydomain.com", Deleted=false}); //Matches true

This can be simply done by Linq where you attach additional operators to the query object.这可以通过 Linq 简单地完成,您可以在其中将附加运算符附加到查询对象。 Here is an example.这是一个例子。

query = db.Contacts.Where( ... );
 query = query.Where( ... );
 query = query.Where( ... );

This is a more simpler and short solution.这是一个更简单、更简短的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM