如何针对分层对象列表动态构建和存储复杂的linq查询?

时间:2017-03-29 03:51:50

标签: c# linq linq-to-objects dynamic-linq

我有层次结构中的对象列表。我想根据客户公司设置并存储在数据库中的“条件”,针对该对象列表构建复杂的LINQ查询。所以我需要在运行时构建它们,但因为每当客户端的用户更新或刷新数据时它们都会重复运行,我想将LINQ查询存储在对象中,而不是每次都重建它们。

我查看了ScottGu关于Dynamic LINQ的博客 还有关于使用expression trees的文章。
这些似乎都没有提供足够的解决方案,但我可能无法充分理解它们。当我考虑其他选择时,我担心我正在尝试使用LINQ。

我的对象层次结构:

WorkOrder[]
    Field[]
    Task[]
        Field[]

以下是我想要存储和执行的LINQ查询的示例。我可以根据定义条件的数据库记录合理地构建这种格式。

var query =
from wo in WorkOrders
from woF in wo.Fields
from task in wo.Tasks
from taskF in task.Fields
from taskF2 in task.Fields
where woF.Name == "System Status"
    && woF.Value.Contains("SETC")
    && taskF.Name == "Material"
    && taskF.Value == "Y"
    && taskF2.Name == "Planner"
    && taskF2.Value == "GR5259"
select new
{
    wo_id = wo.ID,
    task_id = task.ID
};

一些考虑因素。

  • 根据用户定义条件的复杂程度,我可能需要或不需要从不同的对象列表中提取:“froms”是动态的。
  • 请注意,在这个示例中,我从task.fields []中拉了两次,所以我把它别名了两次。
  • 示例LINQ结构允许我使用复杂的AND,OR,括号等,我认为动态链接或表达式树不可行。

在我的代码中,我设想:

//1) Retrieve business rules from DB. I can do this.

//2) Iterate through the business rules to build the linq queries.
foreach (BusinessRule br in BusinessRules) {
    //Grab the criteria for the rule from the DB. 

    //Create a linq to object query based on the criteria just built.
    //Add this query to a list for later use.
}

...Elsewhere in application.

//Iterate through and execute the linq queries in order to apply business rules to data cached in the application.
foreach (LinqQuery q in LinqQueries) {
    //Execute the query

    //Apply business rule to the results.
}

非常感谢您的想法,努力和想法。

2 个答案:

答案 0 :(得分:0)

您可以在技术上仅使用LINQ实现所需,但PredicateBuilder是一个很好的实用程序类:

public enum AndOr
{
    And,
    Or
}

public enum QueryableObjects
{
    WorkOrderField,
    TaskField
}

public class ClientCondition
{
    public AndOr AndOr;
    public QueryableObjects QueryableObject;
    public string PropertyName;
    public string PropertyValue;
}

public void PredicateBuilderExample()
{
    var conditions = new List<ClientCondition> {
    {
        new ClientCondition { AndOr = LINQ.AndOr.And,
            QueryableObject = QueryableObjects.WorkOrderField,
            PropertyName = "System Status",
            PropertyValue = "SETC"
        }
    },
    {
        new ClientCondition{AndOr = AndOr.And,
            QueryableObject = QueryableObjects.TaskField,
            PropertyName = "Material",
            PropertyValue = "Y"
        }
    },
    {
        new ClientCondition{AndOr = AndOr.Or,
            QueryableObject = QueryableObjects.TaskField,
            PropertyName = "Planner",
            PropertyValue = "GR5259"
        }
    }
    };

    //Obviously this WorkOrder object is empty so it will always return empty lists when queried.
    //Populate this yourself.
    var WorkOrders = new List<WorkOrder>();

    var wofPredicateBuilder = PredicateBuilder.True<WorkOrderField>();
    var tfPredicateBuilder = PredicateBuilder.True<TaskField>();

    foreach (var condition in conditions)
    {
        if (condition.AndOr == AndOr.And)
        {
            if (condition.QueryableObject == QueryableObjects.WorkOrderField)
            {
                wofPredicateBuilder = wofPredicateBuilder.And(
                    wof => wof.Name == condition.PropertyName &&
                        wof.Value.Contains(condition.PropertyValue));
            }
        }
        if (condition.AndOr == AndOr.Or)
        {
            if (condition.QueryableObject == QueryableObjects.TaskField)
            {
                tfPredicateBuilder = tfPredicateBuilder.Or(
                    tf => tf.Name = condition.PropertyName &&
                        tf.Value.Contains(condition.PropertyValue));
            }
        }
        //And so on for each condition type.
    }

    var query = from wo in WorkOrders
                from woF in wo.Fields.AsQueryable().Where(wofPredicateBuilder)
                from task in wo.Tasks
                from taskF in task.Fields.AsQueryable().Where(tfPredicateBuilder)
                select new
                {
                    wo_id = wo.ID,
                    task_id = task.ID
                };
}

请注意,我使用枚举来限制客户可以向您发送的条件。要拥有真正动态的可查询引擎,您需要使用Reflection来确保您收到的对象名称有效。这似乎是一个相当大的范围,在这一点上,我建议研究一种不同的方法,例如ElasticSearch

另请注意,And和Ors的顺序非常重要。从本质上讲,您允许您的客户针对您的数据构建S​​QL查询,这通常会以泪流满面。你的工作就是限制他们应该查询的适当条件。

答案 1 :(得分:0)

基于与Guillaume的讨论,我只建议在使用高级动态查询生成时注意结果查询的类型。如果您要更改通过SelectAggregate或其他方法返回的内容的形状,您将期望内部类型相应地更改。如果您只是过滤了哪里可以继续添加所需的其他案例,除非您想要OR行为,那么PredicateBuilder之类的东西会有所帮助。如果您想通过JoinZip,...来提取更多数据,那么您要么这样做要过滤,添加到返回的行,并可能更改数据的形状。

我在过去做过很多这方面的事情并且最成功地专注于特定的辅助方法,这些方法允许我需要的常见情况,然后依靠linq表达式树和模式(如访问者模式)来允许自定义表达式在运行时构建。